//前端面试题(https://zhuanlan.zhihu.com/p/24970850?utm_source=wechat_session)(https://wiki.jirengu.com/doku.php?id=%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E9%87%8D%E7%82%B9%E7%AA%81%E7%A0%B4)
//微任务终极考验面试题，一文讲解async/await转换Promise(https://zhuanlan.zhihu.com/p/450906325?utm_id=0)

JavaScript中undefined的秘密(https://zhuanlan.zhihu.com/p/413492921?utm_id=0)
js中scrollIntoView(调用元素的scrollIntoView方法将会滚动祖先容器，使得元素滚动到可见区域。如果祖先容器不具有滚动条，浏览器会继续向上查找最近的可以滚动的祖先容器，直到找到一个可以滚动的容器为止)
scrollLeft,scrollWidth,clientWidth,offsetWidth到底指的哪到哪的距离之完全详解(https://blog.csdn.net/u013063153/article/details/52805223,https://www.cnblogs.com/lem-worm/archive/2007/11/22/968787.html,https://blog.csdn.net/weixin_44100002/article/details/115342861)

//在javascript中，函数都会有一个返回值。返回值可以通过return关键字进行设置'；如果函数里不写return，则函数会返回undefined，可以根据需要判断是否有返回值。
//javascript是一种严格区分大小写的语言，所以 Hello 和 hello 是两个不同的标识符，在输入关键字、变量名、函数名以及所有的标识符时，都必须采取一致的字符大小写形式，
//为了避免输入混乱和语法错误，建议采用小写字符编写代码，在一些特殊情况下可以使用大写形式

//前端网站响应式布局和自适应的区别有哪些(https://www.zhihu.com/pin/1658420630230937600)

//Javascript中扩展运算符的作用及使用场景(https://blog.csdn.net/jiangjunyuan168/article/details/126706007)

// js中的命名规则(https://blog.csdn.net/qq_68299987/article/details/134098148)
// javascript对象属性的命名规则(https://www.cnblogs.com/canger/p/6382944.html)

//基本类型为什么可以调用方法(https://juejin.cn/post/6954336834210136094)

//浏览器输入url会发生什么 - 巨详细完整版(https://zhuanlan.zhihu.com/p/678734779)
//深入解析 EventLoop 和浏览器渲染、帧动画、空闲回调的关系(https://zhuanlan.zhihu.com/p/142742003)
//DOM 更新和渲染时机,DOM 的更新是同步修改，DOM渲染发生在当前事件循环的微任务执行结束后，下次事件循环之前(https://zhuanlan.zhihu.com/p/679354127,https://blog.csdn.net/CRCFeng/article/details/130531662)

//Google I/O 2023(https://zhuanlan.zhihu.com/p/631879733)
//浏览器一帧做了那些事情(https://www.jianshu.com/p/c146ce2bbdf2)
//事件循环（Event Loop）相关概念 及 面试题(https://blog.csdn.net/m0_67841039/article/details/124975370,https://www.cnblogs.com/cuijinlin/p/13722517.html)
单线程是异步产生的原因，事件循环是异步的实现方式
//前端中的i/o操作指磁盘I/O（读写文件）和网络I/O（网络请求和响应）
//宏任务和微任务(https://blog.csdn.net/lrx276/article/details/134527716)
//浏览器中常见的宏任务：script( 整体代码),setTiomeout,setInterval,网络请求和响应,requestAnimationFrame，用户交互事件(DOM事件)的回调函数
//浏览器中常见的微任务：promise.then catch finally中的回调函数，queueMicrotask,MutationObserver(用来监视 DOM 变动。DOM 的任何变动,比如节点的增减、属性的变动、文本内容的变动都会触发MutationObserver事件)


//Http、SSE、Websocket的区别(https://blog.csdn.net/tiansyun/article/details/135638957)
//短轮询、长轮询、SSE 和 WebSocket之间的区别(https://www.cnblogs.com/superlizhao/p/13992807.html)
//长轮询的实现方式
async function subscribe() {
  let response = await fetch("/subscribe");
  if(response.status == 200){
    // 获取并显示消息
    let message = await response.text();
    showMessage(message);
    // 再次调用 subscribe() 以获取下一条消息
    await subscribe();
  }else{
    if (response.status == 502) {
      // 状态 502 是连接超时错误，
      // 连接挂起时间过长时可能会发生，
      // 远程服务器或代理会关闭它
      // 让我们重新连接
      await subscribe();
    }else{
      // 一个 error —— 让我们显示它
      showMessage(response.statusText);
      // 一秒后重新连接
      await new Promise(resolve => setTimeout(resolve, 1000));
      await subscribe();
    }
  }
}
subscribe();
//长轮询的实现方式

// 函数调用栈小结：
// 1.js是单线程，在主进程上运行
// 2.栈顶的执行上下文处于执行中，其它需要排队
// 3.全局上下文只有一个且处于栈底，页面关闭时出栈
// 4.函数执行上下文可存在多个，但应避免递归时堆栈溢出
// 5.函数调用时就会创建新的上下文，即使调用自身，也会创建不同的执行上下文

// let和const定义的变量存放在script作用域中，var定义的存放在window中

//JavaScript中的数据类型包括字符串、数字、布尔、数组、对象等，以下是它们之间互相转换的函数
// 字符串转数字：
// parseInt()函数：把字符串转换为整数。如果字符串以非数字字符开头，则返回NaN。
// parseFloat()函数：把字符串转换为浮点数。如果字符串以非数字字符开头，则返回NaN。
// Number()函数：把字符串转换为数字。如果字符串以非数字字符开头，则返回NaN。

// 数字转字符串：
// toString()方法：将数字转换为字符串。
// String()函数：将数字转换为字符串。

// 字符串转布尔：
// Boolean()函数：把字符串转换为布尔值。如果字符串为空字符串或者为0，则返回false；否则返回true。

// 布尔转字符串：
// toString()方法：将布尔值转换为字符串。
// String()函数：将布尔值转换为字符串。

// 数组转字符串：
// toString()方法：将数组转换为字符串，元素之间用逗号隔开。
// join()方法：与toString()类似，但可以指定元素之间的分隔符。

// 字符串转数组：
// split()方法：将字符串按照指定的分隔符分割成数组。

// 数字转布尔：
// Boolean()函数：将数字转换为布尔值。如果数字为0或NaN，则返回false；否则返回true。

// 布尔转数字：
// Number()函数：将布尔值转换为数字。true转换为1，false转换为0。

// 对象转字符串：
// JSON.stringify()方法：将对象转换为JSON格式的字符串。

// 字符串转对象：
// JSON.parse()方法：将JSON格式的字符串转换为对象。
//js中转函数和new 函数的区别:转函数得到的不一定是对象，new 函数得到的一定是对象


//js中GC中的算法-标记清除算法(可以有效的解决循环引用的问题)
//标记清除算法的核心思想是可达性
//这个算法是设置一个根对象(root object也就是window),垃圾回收器会定时从这个根对象开始(必须牢记是从根对象去向外找,是单项的过程，切记是单项的)，
//找所有从根对象开始有引用到的对象并且打上标记，对于那些没有被引用的对象(也就是没有被标记的对象)就会被清除


// JS中全局变量和局部变量的生命周期
// 1.全局变量销毁时机:
// 1.1浏览器环境：全局变量存储在全局对象（window）中。只要页面没有被卸载（如用户关闭页面或刷新页面），全局变量就会一直存在//  
// 1.2Node.js 环境：全局变量存储在全局对象（global）中，其生命周期与进程的生命周期相同。只要进程没有退出，全局变量就会一直存在 
// 1.3全局变量（无论是基本数据类型还是引用数据类型）的值最终都存储在堆内存中

// 2.局部变量当函数调用的时候就要生成 执行完毕之后就销毁，函数体内部的变量在函数每次被调用就会创建一个新的

// 3.闭包内引用的变量除非手动把闭包函数赋值为null，否则不会被销毁(闭包中的基本数据类型和引用数据类型都存储在堆内存中。这是因为闭包需要保持对这些变量的引用，即使它们的作用域已经结束)
// function fads() {
//   // let boj = function () {

//   // }
//   let asd=10
//   return function () {
//     console.log(asd);
//   }
// }
// console.log(fads() == fads()); //false 因为每次函数fads调用返回的都是新的函数


//js 中函数的length属性
// 1.函数的length是js函数对象的一个属性，函数的length代表形参的个数
// 2.形参的数量不包括不包括剩余参数的个数，仅包括“第一个具有默认值之前的参数个数”
// function assd(a,s,d) {
//   console.log(a, s,d);
// } //length是3
// function assd(a=1, s, d) {

// } //length是0
// function assd(a, s=1, d) {

// } //length是1
// function assd(a, s, d=1) {

// } //length是2





// 在JavaScript中，&&（逻辑与）、||（逻辑或）运算符和if语句的真假值判断规则是相同的，三元运算符是一种简化if-else语句的方式，和三元运算符真假值判断规则也是相同的。它们都遵循JavaScript中定义的真假值判断标准。以下是JavaScript中被认为是假（falsy）的值：
// false
// 0（数字零）
// -0（负零）
// ""（空字符串）
// null
// undefined
// NaN（Not a Number）
// 所有其他值都被认为是真（truthy）。


//js null、undefined与其他任何类型的值相等(null，undefined在==比较时不会被Number()转化为数字类型)比较时，结果都为false,
//只有null == undefined, null == null, undefined == undefined为true，如果是===判断，null === undefined也为false
null == 0  //false
undefined == 0  //false
//在关系运算符中（如<、>、<=、>=）用于比较两个值的大小时，null，undefined会被Number()强制转换成数字类型
null >= 0  //true Number(null)为0
undefined >= 0  //false Number(undefined)为NAN  NaN不等于任何值,甚至是他自身 NaN==NaN为false


//对象相等性（===或==）仅通过引用判断，而非内容
//引用相同的对象不可能数据不同，因为操作的是同一内存，对象相等性（===或==）仅通过引用判断，而非内容


//js中的3个基本包装类型：String、Number、Boolean
var str1 = "hello world"
var str2 = str1.sunstring(5);
//所以实际上str1在调用方法时可以理解为执行了如下过程
// 1 var s1 = new String(str1);
// 2 var str2 = s1.substring(5);
// 3 s1 = null;
//Number、Boolean的值在调用方法的时候也是类似的操作

// JS中includes和indexOf的异同(https://www.jianshu.com/p/bbbb48506b81,https://blog.csdn.net/nanchen_J/article/details/120509505)
// JavaScript中鼠标事件的clientX、clientY、offsetX、offsetY、screenX、screenY(https://www.jianshu.com/p/bb9b87e7d9e1)


//js块级作用域
//1.考虑到环境导致的行为差异太大，应该避免在块级作用域内声明函数。如果确实需要，也应该写成函数表达式，而不是函数声明语句。
//例如
// 块级作用域内部的函数声明语句，建议不要使用
// {
//   let a = 'secret';
//   function f() {
//     return a;
//   }
// }
// 块级作用域内部，优先使用函数表达式
// {
//   let a = 'secret';
//   let f = function () {
//     return a;
//   };
// }
//2.箭头函数的this会忽略对象和块级作用域
//例如
// {
//   let a = 5;
//   let func3 = {
//     sum: () => {
//       console.log('a', this, this.a, 3000)
//     }
//   }
//   func3.sum();//a Window undefined 3000
// }

//js中函数qwe中变量tem的值是由当前函数位置的外层作用域(全局)中的aa，而不是asd函数中的bb。总结函数的上层作用域是由函数当前的位置决定的，而函数中的this是由函数调用时决定的，是函数调用时的对象
// let tem="aa"
// function qwe() {
//   console.log(tem)
// }
// function asd() {
//   let tem = "bb"
//   qwe()
// }
// asd() //aa

// let temsd = "aa"
// let qwase=null

// function aswed() {
//   let temsd =10
//   qwase = function() {
//     console.log(temsd)
//   }
// }
// aswed() //aa
// qwase()
//console.log(this) this在浏览器全局作用域中是window，在node全局作用域中是{}
// 那么this到底是怎么样的绑定规则呢？一起来学习一下吧
//绑定一：默认绑定；
//绑定二：隐式绑定；
//绑定三：显示绑定；
//绑定四：new绑定；
// function aa() {
//   console.log(this)
// }
// var fn = aa.bind("aa") //显示绑定
// fn() //this是类数组 String {0:"a",1:"a",length:2}
//this绑定的优先级 new绑定 > 显示绑定 > 隐式绑定 > 默认绑定


//this 忽略显示绑定和函数的间接引用 这种情况使用默认绑定规则this指向window(this指函数调用时候所在的那个对象)
// function foo() {
//   console.log(this)
// }
// //忽略显示绑定
// foo.call(null) //this指向window
// foo.apply(null) //this指向window
// var ww = foo.bind(null)
// ww()//this指向window

// var obj1 = {
//   foo: function () {
//     console.log(this)
//   }
// }
// var obj2 = {};
// //函数的间接引用
// (obj2.bar = obj1.foo)() //this指向window


//隐式绑定的两种效果相同的写法(谁调用了这个函数，this指向谁(对象))
// var name = "window"
// var object = {
//   name: "123",
//   foo: function () {
//     console.log(this.name)
//   }
// }
// object.foo(); //123
// (object.foo)() //123效果和object.foo()一样


//在数组的原型上添加一个方法
// Array.prototype.com1 = function () {
//   console.log(this);//[1,2,3]
// };
// [1,2,3].com1()


//在对象的原型上添加一个属性和方法
// Object.prototype.com2 = function () {
//   console.log(this);// name: "xiao" }
// };
// Object.prototype.tens = "hen"
// let qwe = { name: "xiao" }
// qwe.com2()
// console.log(qwe.tens);
// // 任何一个构造函数都是继承自Object函数
// function Foo() { }
// let tem = new Foo()
// tem.com2()
// console.log(tem.tens);


//js模拟实现一个自己的call方法
// Function.prototype.myCall = function (thisArg, ...args) {
//   let _symbol = Symbol()
//   thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg) : window
//   thisArg[_symbol] = this
//   let result = thisArg[_symbol](...args)
//   delete thisArg[_symbol]
//   return result
// }
// function foo(...args) {
//   console.log(this, args);
//   return args.reduce((pre,next)=>pre+next)
// }
// let result1 = foo.myCall({ fn: function () { } }, 20, 30)
// foo.call("",1)
// console.log(result1);
//js模拟实现一个自己的Apply方法
// Function.prototype.myApply = function (thisArg, args) {
//   let _symbol = Symbol()
//   thisArg = thisArg !== undefined && thisArg !== null ? Object(thisArg) : window
//   thisArg[_symbol] = this
//   let result = thisArg[_symbol](...args)
//   delete thisArg[_symbol]
//   return result
// }
//js模拟实现一个自己的Bind方法
// Function.prototype.myBind = function (thisArg, ...args1) {
//   let _symbol = Symbol()
//   thisArg = thisArg !== undefined && thisArg !== null ? Object(thisArg) : window
//   thisArg[_symbol] = this
//   return (...args2) => {
//     let result = thisArg[_symbol](...args1, ...args2)
//     delete thisArg[_symbol]
//     return result
//   }
// }



//js模拟实现一个自己的bind方法
// Function.prototype.myBind = function (thisArg, ...args1) {
//   let _symbol = Symbol()
//   thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg) : window
//   thisArg[_symbol] = this
//   return function (...args2) {
//     let result = thisArg[_symbol](...args1,...args2)
//     delete thisArg[_symbol]
//     return result
//   }
// }
// function foo(...args) {
//   console.log(this, args);
//   return args.reduce((pre, next) => pre + next)
// }
// let result2 = foo.myBind({ age:100 }, 20, 30)
// console.log(result2(40,50));


//arguments可以了解下，不推荐使用了，推荐使用剩余参数的写法...args
// function abc() {
    // arguments是个带length的类数组对象 { '0': 1, '1': 2, '2': 3，length:3 }
// //arguments可以获取length也可以通过索引获取arguments中的某个值arguments[index]
//   console.log(arguments, arguments.length, arguments[0], Array.from(arguments), [...arguments], Array.prototype.slice.call(arguments));
// }
// abc(1, 2, 3) 
//类数组不能使用数组的方法，如果想要用数组方法需要先把类数组转成数组(类数组的对象具有length属性，类数组对象转化成数组必须用Array.from，类数组不能用forEach方法，转化成数组可以用)
//把类数组转化成数组的方式1 [...arguments] 2 Array.from(arguments) 3 Array.prototype.slice.call(arguments) 推荐使用第一种方式或第二种
//js模拟实现一个自己的slice
Array.prototype.mySlice = function (start, end) {
  let list = []
  start = start ? start : 0
  end = end ? end : this.length
  for (let i = start; i < end; i++) {
    list.push(this[i])
  }
  return list
}
// function fntem() {
//   console.log(Array.prototype.mySlice.call(arguments));
// }
// fntem(10, 20, 30)


//js中能用纯函数就尽可能用纯函数，但不是必须用，比如function case(key,info){localStorage.setItem(key,info)}这个就不是纯函数，因为函数中业务逻辑需要这么写，纯函数就没法写了
//纯函数的定义
//1.一个函数的返回结果只依赖于它的参数或者说确定的输入，会产生确定的输出(确定的输入，会产生确定的输出意思是输入决定输出，一个输入只能对应一个输出。不同的输入可以有相同的输出，但是相同的输入不能有不同的输出)
//2.并且在执行过程里面没有副作用
//什么又是副作用呢？
//副作用的意思就是在执行一个函数的时候，除了返回函数值以外，还对调用函数产生了附加的影响，比如修改了全局变量，修改了参数或者改变外部的存储。
//纯函数在执行的过程中不会产生这样的副作用，有副作用的话往往容易产生不易发觉的BUG
// 纯函数优点：
// 1.纯函数非常容易做单元测试，不需要考虑到上下文环境，只需要考虑输入和输出
// 2.纯函数不那么复杂，更容易调试，易于组合，易于并行化。
//foo函数是一个纯函数
// function foo(num1, num2) {
//   return num1 * 2 + num2 * num2
// }
// test是一个纯函数
// function test(info) {
//   return {
//     ...info,
//     age: 100
//   }
// }
//fn是一个纯函数
// const fn = (b) => {
//   const obj = { x: 1 }
//   obj.x = 2
//   return obj.x + b
// }
//不是纯函数的有哪些呢？例子如下
//常用的能改变原数组的7个方法都不是纯函数
// bar不是一个纯函数, 因为它修改了外界的变量
// var name = "abc"
// function bar() {
//   console.log("bar其他的代码执行")
//   name = "cba"
// }
// bar()//baz也不是一个纯函数, 因为我们修改了传入的参数
// function baz(info) {
//   info.age = 100
// }
// var obj = { name: "why", age: 18 }
// baz(obj)
// console.log(obj)


//柯里化
// function _koo(x,y,z) {
//   return x + y + z
// }
// function koo(x) {
//   return function (y) {
//     return function (z) {
//       return x + y + z
//     }
//   }
// }
// koo函数可以用es6写成 const _koo=x=>y=>z=>x+y+z  _koo(10)(20)(30) //60


//柯里化的优势
// 1.单一职责，把多行代码分散到不同的函数中，方便管理维护
// 例如：
// function loo(x) {
//   x = x + 2
//   return function (y) {
//     y = y * 2
//     return function (z) {
//       z = z * z
//       return x+y+z
//     }
//   }
// }
// 2.重复代码逻辑复用0
// function zoo(x) {
//   return function (y) {
//     return function (z) {
//       return x + y + z
//     }
//   }
// }
// const _zoo = zoo(10)(20) //10和20是要固定传的参数，这样做起到逻辑复用的效果
// _zoo(30)
// _zoo(40)
// _zoo(50)


//给一个函数传入一个函数参数把他变成柯里化函数
// function fncurring(x, y, z) {
//   return x + y + z
// }
// function curring(fn) {
//   function bar(...args) {
//     if (fn.length <= args.length) {
//       return fn.apply(this, args)
//     } else {
//       return function (...args2) {
//         return bar.apply(this, [...args, ...args2]) //这里绑定this需要向老师确认下是否必须
//       }
//     }

//   }
//   return bar
// }
// const _curring = curring(fncurring)
// console.log(_curring(10), _curring(10, 20, 30),_curring(10, 20)(30), _curring(10)(20, 30), _curring(10)(20)(30), "63000");


//封装一个最简单的组合函数(compose)
// function double(count) {
//   return count * 2
// }
// function square(count) {
//   return count ** 2
// }
// function composeFn(double, square) {
//   return function (count) {
//     return square(double(count))
//   }
// }
// const _composeFn = composeFn(double, square)
// console.log(_composeFn(10), "52");

//封装一个通用的组合函数(compose)
// function sum(a, b) {
//   return a + b
// }
// function len(str) {
//   return str.length
// }
// function addpro(str) {
//   return "$" + str
// }

//第一种实现
// function compose(...fns) {
//   return (...args) => {
//     let pop = fns.pop()
//     return fns.reduceRight((pre,current) => {
//       return current(pre)
//     }, pop(...args))
//    }
// }
//第二种实现
//第一次pre是addpro函数，current是len函数
//第二次pre是function(...args){return addpro(len(...args))}，current是sum函数
//最后返回值是function(...args){return function(...args){return addpro(len(...args))(sum(...args))}}
// function compose(...fns) {
//   return fns.reduce((pre,current) => {
//     return function (...args) {
//       return pre(current(...args))
//     }
//   })

// }
//第二种实现的简写
// const compose = (...fns) => fns.reduce((pre, current) => (...args) => pre(current(...args)) )
// let rt = addpro(len(sum("a","b"))) //和下面两行的效果是相同的
// const result = compose(addpro, len, sum)
//  console.log(result("a","b"));



//创建一个对象 1使用new创建 2使用字面量形式创建 推荐使用第二种方案创建
//1.
// let object1 = new Object();//new Object()值是{}
// object1.name = "xiaoming"
// object1.age = 18
// console.log(object1);
//2.
// let object2 = {
//   name: "xiaoming",
//   age:18
// }
/*上面两种创建对象它们的属性都是直接定义在对象内部的,这样来做的时候我们就不能对属性进行一些限制：比如某个属性是否可以被删除
添加，修改，枚举等等,如果我们想要对一个属性进行比较精准的操作控制，那么我们就可以使用属性描述符,
通过属性描述符可以精准的添加或修改对象的属性,属性描述符需要使用 Object.defineProperty 来对属性进行添加或者修改；
*/
//Object.defineProperty用法 1.数据属性描述符 2.存取属性描述符
// 1.数据属性描述符
// name和age虽然没有使用属性描述符来定义, 但是它们也是具备对应的特性的
// value: 赋值的value
// configurable: true
// enumerable: true
// writable: true
// var obj = {
//   name: "why",
//   age: 18
// }
// 用了属性描述符, 那么会有默认的特性
//Object.defineProperty(obj, "address", {
// value: "北京市", // 默认值undefined
// address属性是否可以删除/也不可以重新定义属性描述符
// configurable: false, // 默认值false
// address属性是否可以枚举
// enumerable: true, // 默认值false
// address属性是否可以修改赋值
// writable: false // 默认值false
//})
// 测试configurable的作用
// delete obj.name
// console.log(obj.name)
// delete obj.address
// console.log(obj.address)
// Object.defineProperty(obj, "address", {
//   value: "广州市",
//   configurable: true
// })
// 测试enumerable的作用
// console.log(obj)
// for (var key in obj) {
//   console.log(key)
// }
// console.log(Object.keys(obj))
// 测试Writable的作用
// obj.address = "上海市"
// console.log(obj.address)


//2.存取属性描述符
// var obj = {
//   name: "why",
//   age: 18,
//   _address: "北京市"
// }
// 1.隐藏某一个私有属性_address不希望被外界直接使用和赋值(其实是可以直接obj._address访问的_address的值的)
// 2.如果我们希望截获某一个属性它访问和设置值的过程时, 也会使用存取属性描述符
// Object.defineProperty(obj, "address", {
//   enumerable: true,
//   configurable: true,
//   writable: false,
//   get: function () {
//     foo()
//     return this._address
//   },
//   set: function (value) {
//     bar()
//     this._address = value
//   }
// })
// console.log(obj.address)
// obj.address = "上海市"
// console.log(obj.address)
// function foo() {
//   console.log("获取了一次address的值")
// }
// function bar() {
//   console.log("设置了addres的值")
// }


//定义多个属性描述符
var obj = {
  // 私有属性(js里面是没有严格意义的私有属性)
  _age: 18,
  _eating: function () { },
  // set age(value) {
  //   this._age = value
  // },
  // get age() {
  //   return this._age
  // }
}
// //  Object.defineProperties(obj, {
// //   name: {
// //     configurable: true,
// //     enumerable: true,
// //     writable: true,
// //     value: "why"
// //   },
// //   //下面age的效果和上面obj中的set age，get age效果是一样的，用哪一个都可以
// //   age: {
// //     configurable: true,
// //     enumerable: true,
// //     get: function () {
// //       return this._age
// //     },
// //     set: function (value) {
// //       this._age = value
// //     }
// //   }
// // })
// obj.age = 19
// console.log(obj.age)
// console.log(obj, "9654")
// // 获取某一个特性属性的属性描述符
// console.log(Object.getOwnPropertyDescriptor(obj, "name"))
// console.log(Object.getOwnPropertyDescriptor(obj, "age"))
// // 获取对象的所有属性描述符
console.log(Object.getOwnPropertyDescriptors(obj), obj, 200)


//Object.freeze()的用法
/*Object.freeze() 方法可以冻结一个对象，冻结指的是不能向这个对象添加新的属性，
不能修改其已有属性的值，不能删除已有属性，以及不能修改该对象已有属性的可枚举性、可配置性、可写性。
也就是说，这个对象永远是不可变的。该方法返回被冻结的对象。*/
// var obj = {
//   name: 'why',
//   age: 18
// }
// let sss = Object.freeze(obj)
// obj.name = "kobe"
// obj.height = 1.88
// delete obj.age
// console.log(obj)//{name: 'why',age: 18}
// console.log(obj, sss === obj)//true


// 如果一个函数被使用new操作符调用了，那么它会执行如下操作：
// 1.在内存中创建一个新的对象（空对象）；
// 2.这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性；（后面详细讲）；
// 3.构造函数内部的this，会指向创建出来的新对象；
// 4.执行函数的内部代码（函数体代码）；
// 5.如果构造函数没有显示返回一个对象(return 基本类型数据，还是返回创建出来的新对象，这个特别注意)，则返回创建出来的新对象，否则返回return后面的对象
// function Person1() { return 基本类型数据 }  //new Person1()结果是{} 特别注意
// function Person2() {  }  //new Person2()结果是{}  new Person1()和new Person2()结果一样
// function Person3() { return {name:"123"} }  //new Person3()结果是{name:"123"}



/*任意一个对象上面都有一个隐式原型属性[[prototype]],可以通过__proto__(__proto__是浏览器提供的，有兼容性问题)访问
也可以通过Object.getPrototypeOf()（ECMA提供的方式）方法可以获取到,这个隐式原型属性在浏览器打印的值是{constructor:当前的构造函数本身},
浏览器中有constructor是因为方便我们调试才显示的，并且颜色是灰色的
node中打印的值是{},其实这个{}中是有constructor:当前的构造函数本身，但是constructor是不可枚举所以打印不出来
*/
// let obja = { name: "xiap" }
// let objb = {}
// console.log(obja.__proto__, Object.getPrototypeOf(objb), "5210");//{}
// class Point { /*...*/ }

// class ColorPoint extends Point { /*...*/ }

// console.log(Object.getPrototypeOf(ColorPoint) === ColorPoint.__proto__,
// Object.getPrototypeOf(ColorPoint) === Point, ColorPoint.__proto__ === Point)
//true true true 其实ColorPoint.__proto__=Point 这个继承的作用是实现子类继承父类的静态方法


//函数也是一个对象所以也有一个隐式原型属性[[prototype]]，同时也有一个自己特有的显示原型prototype(ECMA很早就有的)属性
// function fon() {
// }
// console.log(fon.prototype, "6965");
/*谷歌浏览器打印的值是{name:"wang",constructor:function fon(){}}
(能打印出来值是因为浏览器方便我们调试才显示的，并且颜色是灰色的) 它里面的name属性是下面添加的  node中打印出来的值是{ }
其实这个{}中是有constructor:当前的构造函数本身，name:"wang"，但是constructor和name是不可枚举所以打印不出来*/
// console.log(Object.getOwnPropertyDescriptors(fon.prototype), "9635")//{constructor:{configurable: true,enumerable: false,value:fon,writable: true}}
// let f1 = new fon()
// let f2 = new fon()
// // //f1，f2对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性//意思就是new fon().__proto__ = fon.prototype
// console.log(f1.__proto__ === fon.prototype)//true
// console.log(f2.__proto__ === fon.prototype)//true
// f1.__proto__.name = "wang"
// //f1.__proto__.name === f2.__proto__.name === fon.prototype.name //他们都是相等的
// console.log(f2.name, f2.__proto__.name === fon.prototype.name);
// console.log(fon.prototype.constructor, fon.prototype.constructor.name,);//[Function: fon] fon


//直接修改整个prototype对象
// fon.prototype = {
//   // constructor: fon,
//   name: "why",
//   age: 18,
//   height: 1.88
// }
// let f1 = new fon()
// console.log(f1.name, f1.age, f1.height)
// // 真实开发中我们可以通过Object.defineProperty方式添加constructor
// Object.defineProperty(fon.prototype, "constructor", {
//   enumerable: false,
//   configurable: true,
//   writable: true,
//   value: fon
// })


// 规范: 构造函数的首字母一般是大写(构造函数中的普通方法在Xxx.prototype上,静态方法在Xxx上)
// function Person(name, age, height, address) {
//   this.name = name
//   this.age = age
//   this.height = height
//   this.address = address

//   this.eating = function () {
//     console.log(this.name + "在吃东西~")
//   }

//   this.running = function () {
//     console.log(this.name + "在跑步")
//   }
// }
// var p1 = new Person("张三", 18, 1.88, "广州市")
// var p2 = new Person("李四", 20, 1.98, "北京市")
// console.log(p1)
// console.log(p2)
// p1.eating()
// p2.eating()


//获取一个对象中的属性的执行过程
// let obj = {
//   name: "why",
//   age: 18
// }
// [[get]]操作  获取address属性
// 1.在当前的对象中查找属性和方法
// 2.如果没有找到, 这个时候会去原型链(__proto__)对象上查找
// obj.__proto__ = {
// }
// //原型链
// obj.__proto__.__proto__ = {

// }
// obj.__proto__.__proto__.__proto__ = {
//   address: "上海市",
//   eating: function () {
//     console.log(555);
//   }
// }
// console.log(obj.address, obj.eating(), "99999")


//对象和函数的原型区别
// let asd = { name: "hexiao" }
// console.log(asd.__proto__, asd.__proto__.__proto__, Object.getOwnPropertyDescriptors(asd.__proto__), "000")

// function qwe() { }
// console.log(qwe.prototype, qwe.prototype.__proto__, qwe.prototype.__proto__.__proto__,
//   Object.getOwnPropertyDescriptors(qwe.prototype), Object.getOwnPropertyDescriptors(qwe.prototype.__proto__), "1111")


//构造函数和原型的继承-寄生组合式继承
// function createObject(o) {
//   function Fn() { }
//   Fn.prototype = o
//   return new Fn()
// }
// function inheritPrototype(SubType, SuperType) {
//   //createObject(SuperType.prototype)等价于Object.create(SuperType.prototype)
//   // SubType.prototype = createObject(SuperType.prototype)
//   SubType.prototype = Object.create(SuperType.prototype)
//   Object.defineProperty(SubType.prototype, "constructor", {
//     enumerable: false,
//     configurable: true,
//     writable: true,
//     value: SubType
//   })
// }

// function Person(name, age, friends) {
//   this.name = name
//   this.age = age
//   this.friends = friends
// }
// Person.prototype.running = function () {
//   console.log("running~")
// }
// Person.prototype.eating = function () {
//   console.log("eating~")
// }

// function Student(name, age, friends, sno, score) {
//   Person.call(this, name, age, friends)
//   this.sno = sno
//   this.score = score
// }

// inheritPrototype(Student, Person)

// Student.prototype.studying = function () {
//   console.log("studying~")
// }

// var stu = new Student("why", 18, ["kobe"], 111, 100)
// console.log(stu)
// stu.studying()
// console.log(stu.running, "96552");
// stu.running()
// stu.eating()
// console.log(stu.__proto__.constructor.name)


//判断对象中是否有某个属性
// var obj = {
//   name: "why",
//   age: 18
// }
// //给info对象添加address属性
// var info = Object.create(obj, {
//   address: {
//     value: "北京市",
//     enumerable: true
//   }
// })
// //hasOwnProperty方法判断
// console.log(info.hasOwnProperty("address"), info)//true
// console.log(info.hasOwnProperty("name"))//false

// in 操作符: 不管在当前对象还是原型中返回的都是true
// console.log("address" in info)//true
// console.log("name" in info)//true
// // for in
// for (var key in info) {
//   console.log(key)//name,age,address
// }


//instanceof和isPrototypeOf用法，其实它们实现的是同一个效果，只是用法不同(instanceof可以正确的判断对象的类型,instanceof只能正确判断引用数据类型，而不能判断基本数据类型)
//instanceof定义：判断一个构造函数的prototype是否在某个实例对象的原型链上
// function createObject(o) {
//   function Fn() { }
//   Fn.prototype = o
//   return new Fn()
// }
// function inheritPrototype(SubType, SuperType) {
//   //createObject(SuperType.prototype)等价于Object.create(SuperType.prototype)
//   // SubType.prototype = createObject(SuperType.prototype)
//   SubType.prototype = Object.create(SuperType.prototype)
//   Object.defineProperty(SubType.prototype, "constructor", {
//     enumerable: false,
//     configurable: true,
//     writable: true,
//     value: SubType
//   })
// }
// function Person() {

// }
// function Student() {

// }

// inheritPrototype(Student, Person)

// console.log(Person.prototype.__proto__ === Object.prototype)//true
// var stu = new Student()
// console.log(stu instanceof Student) // true
// console.log(stu instanceof Person) // true
// console.log(stu instanceof Object) // true
// //isPrototypeOf定义：判断一个对象是否在一个实例对象的原型链上
// console.log(Student.prototype.isPrototypeOf(stu))//true
// console.log(Person.prototype.isPrototypeOf(stu))//true
// console.log(Object.prototype.isPrototypeOf(stu))//true

// var obj = {
//   name: "why",
//   age: 18
// }

// var info = Object.create(obj)
// console.log(obj.isPrototypeOf(info))//true
// console.log(info.__proto__ === obj);//true


//类在js中用的不是很多，推荐使用函数(函数是一等公民，vue和react都是函数式编程了)
//class类中constructor构造函数和方法的定义
// var names = ["abc", "cba", "nba"]
// class Person {
//   constructor(name, age) {
//     this.name = name
//     this.age = age
//     this._address = "广州市"
//   }

//   // 普通的实例方法
//   // 创建出来的对象进行访问
//   // var p = new Person()
//   // p.eating()
//   eating() {
//     console.log(this.name + " eating~")
//   }

//   running() {
//     console.log(this.name + " running~")
//   }

//   // 类的访问器方法
//   get address() {
//     console.log("拦截访问操作")
//     return this._address
//   }

//   set address(newAddress) {
//     console.log("拦截设置操作")
//     this._address = newAddress
//   }

//   // 类的静态方法(类方法)
//   // Person.createPerson()
//   static randomPerson() {
//     var nameIndex = Math.floor(Math.random() * names.length)
//     var name = names[nameIndex]
//     var age = Math.floor(Math.random() * 100)
//     return new Person(name, age)
//   }
// }
// var p = new Person("why", 18)
// p.eating()
// p.running()
// console.log(p.address)
// p.address = "北京市"
// console.log(p.address)

// // console.log(Object.getOwnPropertyDescriptors(Person.prototype))

// for (var i = 0; i < 50; i++) {
//   console.log(Person.randomPerson())
// }


/*类中的继承(任何一个类默认都是继承自Object类,js中只支持单继承，
也就是只能有一个父类，但是可以通过混入的方式继承自多个类，一个类继承自多个类使用场景很少只作为了解即可)*/
// class Person {
//   constructor(name, age) {
//     this.name = name
//     this.age = age
//     this.x = 1;
//     //new.target指向当前正在执行的函数
//     console.log(new.target.name);
//   }
//   running() {
//     console.log(this.name + " running~")
//   }

//   eating() {
//     console.log(this.name + " eating~")
//   }
//   print() {
//     console.log(this.x);
//   }
//   personMethod() {
//     console.log("处理逻辑1")
//     console.log("处理逻辑2")
//     console.log("处理逻辑3")
//   }

//   static staticMethod() {
//     console.log(this.x);
//     console.log("PersonStaticMethod")
//   }
// }

// class Student extends Person {
//   constructor(name, age, sno) {
//     /*super(name, age虽然代表了父类Person的构造函数，但是返回的是子类Student的实例，
//     即super内部的this指的是Student的实例，因此super(name, age)在这里相当于
//     相当于Person.prototype.constructor.call(this, name, age)*/
//     super(name, age)
//     this.sno = sno
//     this.x = 2;
//   }
//   studying() {
//     console.log(this.name + " studying~")
//   }
//   // 子类对父类的方法的重写
//   running() {
//     console.log("student " + this.name + " running")
//   }
//   //ES6规定，在子类普通方法中通过super调用父类的方法时，方法内部的this指向当前的子类实例
//   m() {
//     //super.print()相当于Person.prototype.print.call(this)
//     super.print();
//   }
//   // 重写personMethod方法
//   personMethod() {
//     //super作为对象时，在普通方法中，指向父类的原型对象；在静态方法中，指向父类。
//     // 复用父类中的处理逻辑
//     super.personMethod()

//     console.log("处理逻辑4")
//     console.log("处理逻辑5")
//     console.log("处理逻辑6")
//   }
//   // 重写静态方法
//   static staticMethod() {
//     console.log(super.studying, "9666");//undefined
//     //super.staticMethod()相当于Person.staticMethod.call(this)
//     super.staticMethod()
//     console.log("StudentStaticMethod")
//   }
// }

// Person.staticMethod();//undefined
// Student.x = 33
// let stu = new Student("why", 18, 111)//new.target.name是Student 在super()执行时，new.target.name它指向的是子类Student的构造函数
// console.log(stu)
// stu.m()//2
// stu.eating()
// stu.running()
// stu.personMethod()
// Student.staticMethod()


//继承内置类
// class HYArray extends Array {
//   firstItem() {
//     return this[0]
//   }

//   lastItem() {
//     return this[this.length - 1]
//   }
// }

// var arr = new HYArray(1, 2, 3)
// console.log(arr.firstItem(), arr.filter(item => item > 2))//1 HYArray(1) [ 3 ]
// console.log(arr.lastItem())//3


/* 多态: 当对不同的数据类型执行同一个操作时, 如果表现出来的行为(形态)不一样, 那么就是多态的体现
(下面的代码就可以叫多态, 实际上我们经常写这样的代码, 只是没有一个官网的叫法而已,知道这个概念即可)*/
// function calcArea(foo) {
//   console.log(foo.getArea())
// }
// var obj1 = {
//   name: "why",
//   getArea: function () {
//     return 1000
//   }
// }
// class Person {
//   getArea() {
//     return 100
//   }
// }
// var p = new Person()
// calcArea(obj1)
// calcArea(p)
// // 也是多态的体现
// function sum(m, n) {
//   return m + n
// }
// sum(20, 30)
// sum("abc", "cba")


//字面量增强写法
// var name = "why"
// var age = 18
// var obj = {
//   // 1.property shorthand(属性的简写)
//   name,
//   age,

//   // 2.method shorthand(方法的简写)
//   foo: function () {
//     console.log(this)
//   },
//   bar() {
//     console.log(this)
//   },
//   baz: () => {
//     console.log(this)
//   },

//   // 3.computed property name(计算属性名)
//   [name + 123]: 'hehehehe'
// }
// obj.baz()
// obj.bar()
// obj.foo()
// // obj[name + 123] = "hahaha"
// console.log(obj)


//数组解构
var names = ["abc", "cba", "nba"]
// var item1 = names[0]
// var item2 = names[1]
// var item3 = names[2]
// 对数组的解构: []
// const [item1, item2, item3] = names
// console.log(item1, item2, item3)
// item1 = 20
// console.log(item1);//报错 const定义的变量item1不能被赋值
// // 解构后面的元素
// var [, , itemz] = names
// console.log(itemz)
// // 解构出一个元素,后面的元素放到一个新数组中
// var [itemx, ...newNames] = names
// console.log(itemx, newNames)
// // 解构的默认值
// var [itema, itemb, itemc, itemd = "aaa"] = names
// console.log(itemd)


//对象解构
// var obj = {
//   name: "why",
//   age: 18,
//   height: 1.88
// }
// 对象的解构: {}
// var { name, age, height } = obj
// name="455"
// console.log(name, age, height)//报错 const定义的变量name不能赋值
// var { age } = obj
// console.log(age)
//变量从新命名为newName
// var { name: newName } = obj
// console.log(newName)
// //变量从新命名为newAddress并赋值默认值
// var { address: newAddress = "广州市" } = obj
// console.log(newAddress)

// function foo(info) {
//   console.log(info.name, info.age)
// }
// foo(obj)
// function bar({ name, age }) {
//   console.log(name, age)
// }
// bar(obj)


// ES6的代码块级作用域(开发过程中优先使用const，如果明确知道某个变量要被修改的时候在用let)
// 对let/const/function/class声明的类型是有效
// {
//   let foo = "why"
//   function demo() {
//     console.log("demo function")
//   }
//   class Person { }
// }
// console.log(foo) // foo is not defined
// 不同的浏览器有不同实现的(大部分浏览器为了兼容以前的代码, 让function是没有块级作用域)
//demo() //demo function 可以打印出来值
//var p = new Person() // Person is not defined

//if,switch,for循环中的{}都是块级作用域

//块级作用域的应用场景
//const btns = document.getElementsByTagName('button')
// for (var i = 0; i < btns.length; i++) {
//   (function(n) {
//     btns[i].onclick = function() {
//       console.log("第" + n + "个按钮被点击")
//     }
//   })(i)
// }
// console.log(i)

//这里的let不能换成const,因为i++,i的值不停的变化
// for (let i = 0; i < btns.length; i++) {
//   btns[i].onclick = function () {
//     console.log("第" + i + "个按钮被点击")
//   }
// }
//for循环的执行过程
// {
//   let i = 0
//   btns[0].onclick = function () {
//     console.log("第" + i + "个按钮被点击")
//   }
// }

// {
//   let i = 1
//   btns[1].onclick = function () {
//     console.log("第" + i + "个按钮被点击")
//   }
// }

// {
//   let i = 2
//   btns[2].onclick = function () {
//     console.log("第" + i + "个按钮被点击")
//   }
// }

// 这里的let可以换成const
// const names = ["abc", "cba", "nba"]
// for (let item of names) {
//   console.log(item)
// }
//for...of的执行过程
// {
//   const item = "abc"
//   console.log(item)
// }

// {
//   const item = "cba"
//   console.log(item)
// }

// {
//   const item = "nba"
//   console.log(item)
// }


// // ES6之前拼接字符串和其他标识符
// const name = "why"
// const age = 18
// const height = 1.88
// // console.log("my name is " + name + ", age is " + age + ", height is " + height)
// // ES6提供模板字符串 ``
// const message = `my name is ${name}, age is ${age}, height is ${height}`
// console.log(message)

//可以写表达式
// const info = `age double is ${age===18?"我们":"他们"}`
// console.log(info)

// function doubleAge() {
//   return age * 2
// }

//也可以调用方法
// const info2 = `double age is ${doubleAge()}`
// console.log(info2)

// //下面这个模块字符串用法了解即可，实际业务代码不用，一些库可能会用到
// // 第一个参数依然是模块字符串中整个字符串, 只是被切成多块,放到了一个数组中
// // 第二个参数是模块字符串中, 第一个 ${}
// function foo(m, n, x) {
//   console.log(m, n, x, '---------')
// }
// // foo("Hello", "World")

// // 另外调用函数的方式: 标签模块字符串
// // foo``

// // foo`Hello World`
// const name1 = "why"
// const age1 = 18
// // ['Hello', 'Wo', 'rld']
// foo`Hello${name1}Wo${age1}rld`


//下面代码中，参数变量a,b是默认声明的，在函数体中，不能用let或const再次声明，否则会报错
// function ssw(a,b) {
//   let a = 10
//   let b=20
// }
// ssw(null, null)

//函数默认值参数
// function qwe({ name="xiaohua",age=20}={}){
//   console.log(name,age)
// }
// qwe()
// // 有默认值的形参最好放到最后,不然想要输出默认值的参数在函数调用时需要写undefined
// function bar(x, y, z = 30) {
//   console.log(x, y, z)
// }
// bar(10, 20)
//bar(, 10, 20) //这样写时会报错的，,号前面必须写undefined或其他数据

// 有默认值的函数的length属性(了解即可实际代码用的比较少，它的规则是从默认值(包括默认值)开始后面的参数都不算length的个数)
// function baz(x, y, z, m=20, n) {
//   console.log(x, y, z, m, n)
// }

// console.log(baz.length)//3 


//剩余参数必须放到最后否则会报错(代码中不推荐在使用arguments了)
// function foo(m, n,...args) {
//   console.log(m, n)
//   console.log(args)

//   console.log(arguments)
// }
// foo(20, 30, 40, 50, 60)


//箭头函数不能作为构造函数(箭头函数内部没有this)
// var bar = () => {
//   console.log(this, arguments)
// }
// console.log(bar.prototype)//undefined
// // bar is not a constructor(构造函数)
// const b = new bar()


//展开运算符
// const names = ["abc", "cba", "nba"]
// const name = "why"
// const info = { name: "why", age: 18 }
// // 1.函数调用时
// function foo(x, y, z) {
//   console.log(x, y, z)
// }
// // foo.apply(null, names)
// foo(...names)
// foo(...name)
// // 2.构造数组时
// const newNames = [...names, ...name]
// console.log(newNames)
// // 3.构建对象字面量时ES2018(ES9)
// const obj = { ...info, address: "广州市", ...names }
// console.log(obj)


//数字的进制(只有十进制常用，其他都不常用仅作了解)
// const num1 = 100 // 十进制
// // b -> binary
// const num2 = 0b100 // 二进制
// // o -> octonary
// const num3 = 0o100 // 八进制
// // x -> hexadecimal
// const num4 = 0x100 // 十六进制 内存地址经常用这个表示
// console.log(num1, num2, num3, num4)
// // 大的数值的连接符(ES2021 ES12)
// const num = 100_00_000_000_000_000
// console.log(num)


//  1.ES6中Symbol的基本使用(es6中支持用Symbol作为对象的属性,我们把它当成唯一不同的字符串理解就行了)
// const s1 = Symbol()
// const s2 = Symbol()
// // console.log(s1 === s2) //false
// // // ES2019(ES10)中, Symbol还有一个描述(description)
// const s3 = Symbol("aaa")
// // console.log(s3.description)//aaa
// // 2.Symbol值作为key
// // 2.1.在定义对象字面量时使用
// const obj = {
//   [s1]: "abc",
//   [s2]: "cba"
// }
// // 3.2.新增属性
// obj[s3] = "nba"
// // 3.3.Object.defineProperty方式
// const s4 = Symbol()
// Object.defineProperty(obj, s4, {
//   enumerable: true,
//   configurable: true,
//   writable: true,
//   value: "mba"
// })
// console.log(obj[s1], obj[s2], obj[s3], obj[s4])
// // 注意: 不能通过.语法获取
// // console.log(obj.s1)
let use1 = { name: "lisi", key: Symbol() }
let use2 = { name: "lisi", key: Symbol() }
let obd = { name }

// // 4.使用Symbol作为key的属性名,在for in遍历或Object.keys等中是获取不到这些Symbol值
// // 需要Object.getOwnPropertySymbols来获取所有Symbol的key
// console.log(Object.keys(obj))
// console.log(Object.getOwnPropertyNames(obj),"5666")
// console.log(Object.getOwnPropertySymbols(obj))
// const sKeys = Object.getOwnPropertySymbols(obj)
// for (const sKey of sKeys) {
//   console.log(obj[sKey])
// }

//  5.Symbol.for(key)/Symbol.keyFor(symbol) 了解即可实际代码几乎不怎么用
// const sa = Symbol.for("aaa")
// const sb = Symbol.for("aaa")
// console.log(sa === sb)

// const key = Symbol.keyFor(sa)
// console.log(key)
// const sc = Symbol.for(key)
// console.log(sa === sc)


//set的用法
//new Set()可以接受数组，类数组，字符串作为参数
//数组去重
// let set = new Set([1, 2, 3, 4, 4]);
//Array.from(set)和[...set]是把set结构转化成数组两种方式
//let newArray = Array.from(set)
//扩展运算符（...）内部使用for...of循环，所以也可以用于 Set 结构
//  let newArray = [...set]
// console.log(newArray, "633");

//接受类数组作为参数
// const set1 = new Set(document.querySelectorAll('div'));
// //size返回Set实例的成员总数
// const _size = set1.size
// console.log(set1, _size, "633");

//接受类字符串作为参数
// const newString = [...new Set('ababbc')].join('')
// console.log(newString, "633");//"abc"

//下面代码向 Set 实例添加了两次NaN，但是只会加入一个。这表明，在 Set 内部，两个NaN是相等的,另外，两个{}对象总是不相等的，因为内存地址不一样
// let set = new Set();
// let a = NaN;
// let b = NaN;
// set.add(a);
// set.add(b);

// set.add({});
// console.log(set.size) // 2

// set.add({});
// console.log(set.size) // 3
// //obj是同一个对象添加两次，但是set内部只有一个
// const obj = {age:45}
// set.add(obj)
// set.add(obj)
// console.log(set)

//Set 实例的属性和方法
//属性
// Set.prototype.constructor：构造函数，默认就是Set函数。
// Set.prototype.size：返回Set实例的成员总数。
//方法
// Set.prototype.add(value)：添加某个值，返回 Set 结构本身
// const s=new Set();
// s.add(1).add(2).add(2);//可以链式调用
// console.log(s);

// Set.prototype.delete(value)：删除某个值，返回一个布尔值，表示删除是否成功。
// Set.prototype.has(value)：返回一个布尔值，表示该值是否为Set的成员。
// Set.prototype.clear()：清除所有成员，没有返回值。

//遍历方法
//由于 Set 结构没有键名，只有键值（或者说键名和键值是同一个值），所以keys方法和values方法的行为完全一致
// let set = new Set(['red', 'green', 'blue']);
// console.log(set, set.size, set.keys().size, set.values().size, set.entries().size);
// console.log(set);
// //new Set(['red', 'green', 'blue'])等同于下面代码
// let allArray = ['red', 'green', 'blue']
// let _set = new Set()
// allArray.forEach(item => {
//   _set.add(item)
// })
// console.log(_set);

// for (let item of set.keys()) {
//   console.log(item); // red green blue
// }
// for (let item of set.values()) {
//   console.log(item);// red green blue
// }
// for (let item of set.entries()) {
//   console.log(item);//["red", "red"] ["green", "green"] ["blue", "blue"]
// }
// set.forEach((item,key) => {
//   console.log(item, key);//red red green green blue blue
// })
// for (const item of set) {
//   console.log(item);//red green blue
//}
//set有size属性，set.keys()，set.values()，set.entries()没有size属性


//WeakSet的使用和注意事项(了解即可)
// 1.WeakSet 的成员只能是对象，而不能是其他类型的值。
/*2.WeakSet 中的对象都是弱引用，如果其他对象都不再引用该对象，那么垃圾回收机制会自动回收该对象所占用的内存，
不考虑该对象还存在于 WeakSet 之中*/
//属性
//WeakSet 没有size属性有constructor属性
//方法 WeakSet 不能遍历，是因为成员都是弱引用，随时可能消失 所以没有forEach,for...of的用法
// WeakSet.prototype.add(value)：向 WeakSet 实例添加一个新成员。
// WeakSet.prototype.delete(value)：清除 WeakSet 实例的指定成员。
// WeakSet.prototype.has(value)：返回一个布尔值，表示某个值是否在 WeakSet 实例之中
//下面是 WeakSet 的另一个例子
//const foos = new WeakSet()
// class Foo {
//   constructor() {
//     foos.add(this)
//   }
//   method() {
//     if (!foos.has(this)) {
//       throw new TypeError('Foo.prototype.method 只能在Foo的实例上调用！');
//     }
//   }
// }


//Map的使用
// const map = new Map([
//   ['name', '张三'],
//   ['title', 'Author']
// ]);
// console.log(map);
// map.size // 2
// map.has('name') // true
// map.get('name') // "张三"
// map.has('title') // true
// map.get('title') // "Author"
//Map构造函数接受数组作为参数，实际上执行的是下面的算法。
// const items = [
//   ['name', '张三'],
//   ['title', 'Author']
// ];

// const map = new Map();

// items.forEach(
//   ([key, value]) => map.set(key, value)
// );

//属性
// Map.prototype.constructor：构造函数，默认就是Map函数。
// Map.prototype.size：返回Map实例的成员总数。
//方法
//1.Map.prototype.set(key, value) set方法设置键名key对应的键值为value，然后返回整个 Map 结构。如果key已经有值，则键值会被更新，否则就新生成该键
// let map = new Map() 
//   .set(1, 'a')
//   .set(2, 'b')
//   .set(3, 'c'); 可以链式调用
//2.Map.prototype.get(key) get方法读取key对应的键值，如果找不到key，返回undefined。
//3.Map.prototype.has(key) has方法返回一个布尔值，表示某个键是否在当前 Map 对象之中
//4.Map.prototype.delete(key) delete方法删除某个键，返回true。如果删除失败，返回false
//5.Map.prototype.clear() clear方法清除所有成员，没有返回值

//遍历方法
// Map.prototype.keys()：返回键名的遍历器。
// Map.prototype.values()：返回键值的遍历器。
// Map.prototype.entries()：返回所有成员的遍历器。
// Map.prototype.forEach()：遍历 Map 的所有成员。

// const map = new Map([
//   ['F', 'no'],
//   ['T', 'yes'],
// ]);
// console.log(map);
// for (let key of map.keys()) {
//   console.log(key);//"F" "T"
// }

// for (let value of map.values()) {
//   console.log(value);//"no" "yes"
// }

// for (let [key, value] of map.entries()) {
//   console.log(key, value);//"F" "no" "T" "yes"
// }

// for (let item of map.entries()) {
//   console.log(item);//["F" "no"] ["T" "yes"]
// }

//Map 结构转为数组结构，比较快速的方法是使用扩展运算符（...）
// const map = new Map([
//   [1, 'one'],
//   [2, 'two'],
//   [3, 'three'],
// ]);
// [...map.keys()];
// [1, 2, 3]

// [...map.values()];
// ['one', 'two', 'three']

//  [...map.entries()];
// [[1,'one'], [2, 'two'], [3, 'three']]

//  [...map]
// [[1,'one'], [2, 'two'], [3, 'three']]
//[...map.entries()]和[...map]结果是一样的
//map有size属性，map.keys()，map.values()，map.entries()没有size属性


//WeakMap的注意事项和使用(需要掌握vue3响应式源码中用这个实现)
//注意事项
//1.WeakMap 的key只能是是对象(null除外)，而不能是其他类型的值。
//2.WeakMap 中的对象都是弱引用，如果其他对象都不再引用该对象，那么垃圾回收机制会自动回收该对象所占用的内存
//使用
//WeakMap.prototype.constructor 构造函数，默认就是WeakMap函数
//WeakMap只有四个方法可用 get()、set()、has()、delete()

//const wm = new WeakMap();
// size、forEach、clear 方法都不存在
// wm.size // undefined
// wm.forEach // undefined
// wm.clear // undefined


//es7中新增的内容
// 1.includes
// const namesArray = ["abc", "cba", "nba", "mba", NaN]
// if (namesArray.indexOf("cba") !== -1) {
//   console.log("包含abc元素")
// }

// if (namesArray.includes("cba", 2)) {
//   console.log("包含abc元素")
// }
// //indexOf没法判断NaN
// if (namesArray.indexOf(NaN) !== -1) {
//   console.log("包含NaN")
// }
// //includes可以判断NaN
// if (namesArray.includes(NaN)) {
//   console.log("包含NaN")
// }

// 2.幂运算 **
// let numb1 = Math.pow(3, 3) //Math.pow(x,y) 方法返回 x 的 y 次幂 es5的用法
// let numb2 = 3 ** 3  //es7的用法
// console.log(numb1, numb2);


//es8中新增的内容
//1.Object.keys,Object.values,Object.entries用法
// const obj = {
//   name: "why",
//   age: 18
// }
// console.log(Object.keys(obj))
// console.log(Object.values(obj))

// 用的非常少
// console.log(Object.values(["abc", "cba", "nba"]))//[ 'abc', 'cba', 'nba' ]

// //Object.values("abc"),Array.from("abc"),[..."abc"]结果是一样的,推荐用[..."abc"]
// console.log(Object.values("abc"),[..."abc"],Array.from("abc"));//[ 'a', 'b', 'c' ]

// console.log(Object.entries(["abc", "cba", "nba"]))//[ [ '0', 'abc' ], [ '1', 'cba' ], [ '2', 'nba' ] ]
// console.log(Object.entries("abc"))//[ [ '0', 'a' ], [ '1', 'b' ], [ '2', 'c' ] ]

//2.padStart和padEnd
//Ees8引入了字符串补全长度的功能。如果某个字符串不够指定长度，会在头部或尾部补全。padStart() 用于头部补全，padEnd() 用于尾部补全
// const message = "Hello World"
// const newMessage = message.padStart(15, "*").padEnd(20, "-")
// console.log(newMessage, message);
// //使用场景(身份证号或银行卡号部分位数遮盖)
// const cardNumber = "321324234242342342341312"
// const lastFourCard = cardNumber.slice(-4)
// const finalCard = lastFourCard.padStart(cardNumber.length, "*")
// console.log(finalCard);

//3.Object.getOwnPropertyDescriptors() 获取一个对象的属性描述符
//4.async await


//es9中新增的内容
// 1.Async iterators：后续迭代器讲解
// 2.Object spread operators：let obj={} let obg1={...obj} 展开运算符
// 3.Promise finally：后续讲Promise讲解


//es10中新增的内容
//1.flat flatMap 用法
// const numbers = [1, 2, [3, 4, [5, 6]]];
// //flat()函数的参数是指定要提取嵌套数组的深度，默认值为 1
// numbers.flat(); //[1, 2, 3, 4, [5, 6]]
// numbers.flat(1); //[1, 2, 3, 4, [5, 6]]

// numbers.flat(2); //[1, 2, 3, 4, 5, 6]

// numbers.flat().flat(); //[1, 2, 3, 4, 5, 6]
// //使用Infinity可以展开任意深度嵌套的数组
// numbers.flat(Infinity)//[1, 2, 3, 4, 5, 6]

//flatMap() 方法是 先执行map() 方法，然后在执行深度为 1 的 flat() 方法。
//flatMap()的使用场景
// const messages = ["Hello World", "hello lyh", "my name is coderwhy"]
// const words = messages.flatMap(item => {
//   return item.split(" ")
// })
// console.log(words)

//2.Object.fromEntries的用法
// const obj = {
//   name: "why",
//   age: 18,
//   height: 1.88
// }
// const entries = Object.entries(obj)
// console.log(entries)//[ [ 'name', 'why' ], [ 'age', 18 ], [ 'height', 1.88 ] ]
// const newObj = Object.fromEntries(entries)
// console.log(newObj)//{ name: 'why', age: 18, height: 1.88 }

//Object.fromEntries的使用场景,把一个url中拼接的参数转化成一个对象
//window.location.search='?name=why&age=18&height=1.88' window.location.search获取url中拼接的参数字符串
const queryString = '?name=why&age=18&height=1.88'
const queryParams = new URLSearchParams(queryString)
// console.log(queryParams, [...queryParams], queryParams.entries() ,"5633");
for (const param of queryParams) {
  console.log(param)
}
/*Object.fromEntries(queryParams),Object.fromEntries(queryParams.entries()),Object.fromEntries([...queryParams])这3种效果一样,
Object.fromEntries()传递的参数既可以是可迭代的对象也可以是entries数组*/
//const paramObj = Object.fromEntries(queryParams.entries())
const paramObj = Object.fromEntries(queryParams)
//const paramObj = Object.fromEntries([...queryParams])
console.log(paramObj)

//3.trimStart和trimEnd的用法
// const message = "    Hello World    "
// console.log(message.trim())
// console.log(message.trimStart())
// console.log(message.trimEnd())

//4.Symbol description：已经讲过了
//5.Optional catch binding：后面讲解try cach讲解


//ES11中新增的内容
// 1.ES11之前 max_safe_integer 大数字的用法(了解即可)
// const maxInt = Number.MAX_SAFE_INTEGER
// console.log(maxInt) // 9007199254740991
// console.log(maxInt + 1)
// console.log(maxInt + 2)
// // ES11之后: BigInt 大数字的用法(了解即可)
// const bigInt = 900719925474099100n
// console.log(900719925474099100+10,"966");
// console.log(bigInt + 10n)
// const num = 100
// console.log(bigInt + BigInt(num), "6335")
// const smallNum = Number(bigInt)
// console.log(smallNum)

//2.空值合并运算 ?? (foo的值是null或undefined时,bar的值是"defualt value",否则bar的值是foo)
// const foo = undefined
// // const bar = foo || "default value"
// const bar = foo ?? "defualt value"
// console.log(bar)

//3.可选链?.
// const info = {
//   name: "why",
//   // friend: {
//   //   girlFriend: {
//   //     name: "hmm"
//   //   }
//   // }
// }
//es5只能用下面的if判断写法
// console.log(info.friend.girlFriend.name)
// if (info && info.friend && info.friend.girlFriend) {
//   console.log(info.friend.girlFriend.name)
// }
// ES11提供了可选链(代码可以这样写)
//console.log(info.friend?.girlFriend?.name)
//info.friend?.girlFriend?.name="632" 这样写会报错 可选链获取的变量不能被赋值，只能获取值才可以用可选链
//console.log('其他的代码逻辑')

// 4.globalThis(获取某个环境下的全局对象 Global Object)
// 在浏览器下
// console.log(window)
// console.log(this)
// 在node下
// console.log(global)

//console.log(globalThis)//在浏览器中是window，在node中是global

// 5.Dynamic Import：后续ES Module模块化中讲解。
// 6.Promise.allSettled：后续讲Promise的时候讲解。
// 7.import meta：后续ES Module模块化中讲解


//es12中新增的内容
// 1.FinalizationRegistry类
// const finalRegistry = new FinalizationRegistry((value) => {
//   console.log("注册在finalRegistry的对象, 某一个被销毁", value)
// })
// let obj = { name: "why" }
// let info = { age: 18 }
// finalRegistry.register(obj, "obj")
// finalRegistry.register(info, "value")
// obj = null
// info = null

// 2.WeakRef类(弱引用)
// WeakRef.prototype.deref: 
// > 如果原对象没有销毁, 那么可以获取到原对象
// > 如果原对象已经销毁, 那么获取到的是undefined
// const finalRegistry = new FinalizationRegistry((value) => {
//   console.log("注册在finalRegistry的对象, 某一个被销毁", value)
// })
// let obj = { name: "why" }
// let info = new WeakRef(obj)

// finalRegistry.register(obj, "obj")

// obj = null
// setTimeout(() => {
//   console.log(info.deref()?.name)
//   console.log(info.deref() && info.deref().name)
// }, 10000)

//3.||=，&&=，??=
// 3.1||= 逻辑或赋值运算
// let message = "hello world"
// message = message || "default value"
// message ||= "default value"
// console.log(message)

// 3.2&&= 逻辑与赋值运算(比较少用了解即可)
// &&
// const obj = {
//   name: "why",
//   foo: function() {
//     console.log("foo函数被调用")

//   }
// }

// obj.foo && obj.foo()

// &&=
// let info = {
//   name: "why"
// }

// // 1.判断info
// // 2.有值的情况下, 取出info.name
// // info = info && info.name
// info &&= info.name
// console.log(info)

// 3.3??= 逻辑空赋值运算
// let message = 0
// message ??= "default value"
// console.log(message)

// 4.Numeric Separator：讲过了；1000_00_00_00 用下划线使得数据更加清晰
// 5.String.replaceAll：字符串替换；


//proxy和Reflect的用法
//监听对象的方式es5用法
// const obj = {
//   name: "why",
//   age: 18
// }

// Object.defineProperty(obj, "name", {
//   get: function() {
//     console.log("监听到obj对象的name属性被访问了")
//   },
//   set: function() {
//     console.log("监听到obj对象的name属性被设置值")
//   }
// })

// Object.keys(obj).forEach(key => {
//   let value = obj[key]
//   Object.defineProperty(obj, key, {
//     get: function () {
//       console.log(`监听到obj对象的${key}属性被访问了`)
//       return value
//     },
//     set: function (newValue) {
//       console.log(`监听到obj对象的${key}属性被设置值`)
//       value = newValue
//     }
//   })
// })
// obj.name = "kobe"
// obj.age = 30

// console.log(obj.name)
// console.log(obj.age)

//es6的用法
// const obj = {
//   name: "why",
//   age: 18
// }
// const objProxy = new Proxy(obj, {
//   // 获取值时的捕获器
//   get: function (target, key) {
//     console.log(`监听到对象的${key}属性被访问了`, target)
//     return target[key]
//   },

//   // 设置值时的捕获器
//   set: function (target, key, newValue) {
//     console.log(`监听到对象的${key}属性被设置值`, target)
//     target[key] = newValue
//   },

//   // 监听in的捕获器
//   has: function (target, key) {
//     console.log(`监听到对象的${key}属性in操作`, target)
//     return key in target
//   },

//   // 监听delete的捕获器
//   deleteProperty: function (target, key) {
//     console.log(`监听到对象的${key}属性in操作`, target)
//     delete target[key]
//   }
// })
// console.log(objProxy.name)
// console.log(objProxy.age)

// objProxy.name = "kobe"
// objProxy.age = 30

// console.log(obj.name)
// console.log(obj.age)

// in操作符
//console.log("name" in objProxy)
// delete操作

// delete objProxy.name
// console.log(objProxy, obj, "96552122");

//Proxy对函数对象的监听(了解即可)
// function foo() { }
// const fooProxy = new Proxy(foo, {
//   apply: function (target, thisArg, argArray) {
//     console.log("对foo函数进行了apply调用")
//     return target.apply(thisArg, argArray)
//   },
//   construct: function (target, argArray, newTarget) {
//     console.log("对foo函数进行了new调用")
//     return new target(...argArray)
//   }
// })
// fooProxy.apply({}, ["abc", "cba"])
// new fooProxy("abc", "cba")

//Reflect和Proxy一起使用(要熟练掌握)
// const obj = {
//   name: "why",
//   age: 18
// }
// const objProxy = new Proxy(obj, {
//   //receiver参数只存在于set和get函数中，receiver其实是objProxy
//   get: function (target, key, receiver) {
//     console.log("get---------")
//     return Reflect.get(target, key) // 返回target[key]的值
//   },
//   set: function (target, key, newValue, receiver) {
//     console.log("set---------")
//     const result = Reflect.set(target, key, newValue)//返回布尔值
//     // if (result) {
//     // } else {
//     // }
//     return result
//   },
//   // 监听in的捕获器
//   has: function (target, key) {
//     console.log(`监听到对象的${key}属性in操作`, target)
//     return Reflect.has(target, key)//返回布尔值
//   },
//   // 监听delete的捕获器
//   deleteProperty: function (target, key) {
//     console.log(`监听到对象的${key}属性in操作`, target)
//     return Reflect.deleteProperty(target, key)//返回布尔值
//   }
// })
// //执行get方法
// console.log(objProxy.name,"000")
// //执行set方法
// objProxy.name = "kobe"
// console.log(objProxy.name, "111")
// // 执行has方法 "name" in objProxy等价于Reflect.has(objProxy, 'name')
// console.log("name" in objProxy, "222")

// // 执行deleteProperty方法 delete objProxy.age等价于Reflect.deleteProperty(objProxy, 'age')
// delete objProxy.age
// console.log(objProxy, obj, "333");
//console.log(objProxy.name, obj)

//receiver参数的作用(了解即可)
// const obj = {
//   _name: "why",
//   get name() {
//     return this._name
//   },
//   set name(newValue) {
//     this._name = newValue
//   }
// }
// const objProxy = new Proxy(obj, {
//   get: function (target, key, receiver) {
//     // receiver是创建出来的代理对象，就是objProxy，
//     console.log("get方法被访问--------", key, receiver)
//     console.log(receiver === objProxy)
//     return Reflect.get(target, key, receiver)//receiver作用把objProxy赋值给obj中set和get中的this
//   },
//   set: function (target, key, newValue, receiver) {
//     console.log("set方法被访问--------", key)
//     console.log(receiver === objProxy)
//     Reflect.set(target, key, newValue, receiver)//receiver作用把objProxy赋值给obj中set和get中的this
//   }
// })
// console.log(objProxy.name)
// objProxy.name = "kobe"

//Reflect中construct作用(了解即可)
// function Student(name, age) {
//   this.name = name
//   this.age = age
// }
// function Teacher() { }
// // const stu = new Student("why", 18)
// // console.log(stu)
// // console.log(stu.__proto__ === Student.prototype)

// // 执行Student函数中的内容, 但是创建出来对象是Teacher对象
// const teacher = Reflect.construct(Student, ["why", 18], Teacher)
// console.log(teacher)
// console.log(teacher.__proto__ === Teacher.prototype)

// let s1 = Symbol("jw")
// const objss = {
//   name: "jt",
//   age: 12,
//   [s1]: "ok"
// }
// //想要获取所有的key不能用for in和forEach,因为拿不到Symbol的值，所以需要用reflect.ownKeys
// console.log(Reflect.ownKeys(objss)) //[ 'name', 'age', Symbol(jw) ]
// const fn1 = function (a, b) {
//   console.log(a, b, 200);
// }
// //自定义apply默认会调用它，
// fn1.apply = function () {
//   console.log("apply");
// }
// fn1.apply() //apply
// //如果想要调用fn1函数本身的apply需要用下面这两个他们是等价的
// //call的作用是把apply方法的中this指向fn1，并让apply方法执行 fn1.apply(null,[1,2])
// Function.prototype.apply.call(fn1, null, [1, 2])
// Reflect.apply(fn1, null, [1, 2])
// //和Reflect搭配的常用方法有apply,proxy(get,set,deleteProperty,has,ownKeys)





//promise的用法

//Promise巧妙的用法
// let wes = null
// new Promise((resolve, reject) => {
//   wes = resolve
// }).then(res => {
//   console.log(1245);
// })

// setTimeout(() => {
//   wes()
// }, 5000)

//promise的注意事项
//1.new Promise(fn)中的参数函数fn会立即被执行
/*2.下面的resolve()和reject()中只会执行第一个也就是resolve()，如果是reject()在第一个也只会执行reject()，不会都执行，
因为Promise状态一旦确定下来, 那么就是不可更改的(锁定)*/

const promise1 = new Promise((resolve, reject) => {

  // reject("00")
  // resolve(11)
  // console.log(123000);//123000
  return 3210
  //throw "错误"
})
console.log(promise1, 963000);//Promise { 11 }
promise1.then((res) => {
  console.log("res", res);//res是执行resolve()  //11
}, err => {
  console.log("err", err);//err是执行reject()
})

// 用setTimeout模拟异步请求
//request.js
// function requestData(url,) {
//   // 异步请求的代码会被放入到executor中
//   return new Promise((resolve, reject) => {
//     // 模拟网络请求
//     setTimeout(() => {
//       // 拿到请求的结果
//       // url传入的是coderwhy, 请求成功
//       if (url === "coderwhy") {
//         // 成功
//         let names = ["abc", "cba", "nba"]
//         resolve(names)
//       } else { // 否则请求失败
//         // 失败
//         let errMessage = "请求失败, url错误"
//         reject(errMessage)
//       }
//     }, 3000);
//   })
// }
// // main.js
// const promise = requestData("coderwhy")
// promise.then((res) => {
//   console.log("请求成功:", res)
// }, (err) => {
//   console.log("请求失败:", err)
// })

/**
 *  const p1=Promise.resolve(parameter)中parameter参数使用注意事项
 *  1> parameter是普通的值或者对象  pending -> fulfilled
 *  2> parameter是Promise
 *    那么当前的p1的状态会由parameter这个Promise来决定
 *    相当于状态进行了移交
 *  3> parameter是对象, 并且这个对象有实现then方法(并且这个对象是实现了thenable接口)
 *    那么也会执行该then方法, 并且由该then方法决定后续状态
 */

// 1.传入Promise的特殊情况
const newPromise = new Promise((resolve, reject) => {
  //resolve("aaaaaa")
  reject("err message")
})

new Promise((resolve, reject) => {
  // pending -> fulfilled
  resolve(newPromise)
}).then(res => {
  console.log("res:", res)
}, err => {
  console.log("err:", err)
  throw "错误"
  return 1230
})

// 2.传入一个对象, 这个对象有then方法
new Promise((resolve, reject) => {
  // pending -> fulfilled
  const obj = {
    then: function (resolve, reject) {
      // resolve("resolve message")
      reject("reject message")
    }
  }
  resolve(obj)
}).then(res => {
  console.log("res:", res)
}, err => {
  console.log("err:", err)
})

// // eatable/runable
// const obj = {
//   eat: function () {

//   },
//   run: function () {

//   }
// }

// Promise的原型上有哪些方法
// console.log(Object.getOwnPropertyDescriptors(Promise.prototype))//获取原型上的属性描述符

/*promise对象中then和catch方法的返回值是Promise.resolve(xxx)或Promise.reject(xxx),
如果then和catch方法中的参数函数中 1.代码逻辑报错(xxx.map is not a function或xxx is not defined) 或 2.抛出错误(throw xxx) 或 3.return Promise.reject(xxx)时,
then和catch方法的返回值是Promise.reject(xxx),如果then和catch方法中的参数函数不是上面3中情况，
then和catch方法的返回值都是Promise.resolve(xxx)*/
//promise对象中then方法使用注意事项
// const promise = new Promise((resolve, reject) => {
//   resolve("hahaha")
// })
// 1.同一个Promise可以被多次调用then方法
// 当我们的resolve方法被回调时, 所有的then方法传入的回调函数都会被调用
// promise.then(res => {
//   console.log("res1:", res)
// })
//第一中情况
// const powws = Promise.resolve(11).then((res) => {
//   console.log(res, 456);
//   throw "cuowu"
// })
// console.log(powws, 4120);
//第二中情况
const powwsss = Promise.reject(11).catch((res) => {
  console.log(res, 456);
  //throw "错误" //throw 语句抛出一个错误,JavaScript会停止执行并抛出错误信息。
  asd.name
  "12".map()
  return Promise.reject(222)  //这行代码不会执行
  console.log(123);
})
console.log(powwsss, 4120);
powwsss.then(res => {
  console.log(res, 789);
}).catch((err) => {
  console.log(err, 6963200);//错误
})
// promise.then(res => {
//   console.log("res2:", res)
// })

// promise.then(res => {
//   console.log("res3:", res)
// })

// 2.then方法传入的 "回调函数: 可以有返回值
// then方法本身也是有返回值的, 它的返回值是Promise

// 2.1 如果我们返回的是一个普通值aaaaaa(数值/字符串/普通对象/undefined), 那么这个普通的值被作为then方法返回新的Promise的resolve值
// promise.then(res => {
//   return "aaaaaa"
// }).then(res => {
//   console.log("res:", res)
//   return "bbbbbb"
// })

// 2.2 如果我们返回的是一个Promise
// promise.then(res => {
//   return new Promise((resolve, reject) => {
//     setTimeout(() => {
//       resolve(111111)
//     }, 3000)
//   })
// }).then(res => {
//   console.log("res:", res)
// })

// 2.3 如果返回的是一个对象, 并且该对象实现了thenable
// promise.then(res => {
//   return {
//     then: function(resolve, reject) {
//       resolve(222222)
//     }
//   }
// }).then(res => {
//   console.log("res:", res)
// })

//promise对象中的catch用法
//1.同一个Promise可以被多次调用catch方法
// 当我们的reject方法被回调时, 所有的catch方法传入的回调函数都会被调用
// const promise = new Promise((resolve, reject) => {
//   reject("hahaha")
// })
// promise.catch(res => {
//   console.log("res1:", res)
// })

// promise.catch(res => {
//   console.log("res2:", res)
// })

// promise.catch(res => {
//   console.log("res3:", res)
// })

//2.当executor抛出异常时, 也是会调用错误(拒绝)捕获的回调函数的
//下面函数中throw和报错都能被catch捕获到
const _promise = new Promise((resolve, reject) => {
  resolve()
  //throw new Error("rejected status")
  "12".map()
})
_promise.then(res => {
  console.log(res, 6520);
  "12".splice() //报错也能被捕获到

}).catch(err => {
  console.log("err1:", err)  //TypeError: "12".splice is not a function
})

Promise.reject(123)
console.log(451);
// _promise.then(res => {
//   // return new Promise((resolve, reject) => {
//   //   reject("then rejected status")
//   // })
//   throw new Error("error message")
// }).catch(err => { //catch优先捕获_promise中的reject或抛出异常throw new Error(),如果_promise中的没有reject或异常在去捕获_promise.then(callback)callback函数中的异常或间接的reject,如果callback函数中没有异常或间接的reject，则catch函数中的参数函数不执行
//   console.log("err2:", err)
//   //throw new Error("error message1233")
//   throw "error message1233"
// }).catch(err => {
//   console.log("err3:", err)
// })

//3.catch函数的返回值用法和then函数一样也是返回new Promise(resolve =>resolve(x))这里的x指catch return value
// const promise = new Promise((resolve, reject) => {
//   reject("111111")
// })
// promise.then(res => {
//   console.log("res:", res)
// }).catch(err => {
//   console.log("err:", err)
//   return "catch return value"
// }).then(res => {
//   console.log("res result:", res)
// }).catch(err => {
//   console.log("err result:", err)
// })

// promise对象中的finally用法(1.没有参数 2.最终都会执行)
// const promise = new Promise((resolve, reject) => {
//    resolve("resolve message")
//   //reject("reject message")
// })
// promise.then(res => {
//   console.log("res:", res)
// }).catch(err => {
//   console.log("err:", err)
// }).finally(() => {
//   console.log("finally code execute")
// })


//函数中try finally中代码的执行顺序

// 类方法Promise.resolve(参数)参数的用法 它和new Promise(resolve=>resolve(参数))参数的用法是一样的
// 1.普通的值
// const promise1 = Promise.resolve({ name: "why" })
// 相当于
// const promise2 = new Promise((resolve, reject) => {
//   resolve({ name: "why" })
// })
// 2.传入Promise
// const promise = Promise.resolve(new Promise((resolve, reject) => {
//   resolve("11111")
// }))
// promise.then(res => {
//   console.log("res:", res)
// })
// 3.传入thenable对象
// const promise = Promise.resolve({
//   then: function (resolve, reject) {
//     resolve(555)
// }})
// promise.then(res => {
//   console.log("res:", res)
// })

//类方法Promise.reject(参数)参数的用法
// const promise = Promise.reject("rejected message")
// 相当于
// const promise2 = new Promsie((resolve, reject) => {
//   reject("rejected message")
// })

// 注意: Promise.reject(参数，它没有resolve传入带有then方法的对象参数的用法)中的参数无论传入什么值下面catch方法中的err就是什么值，直白说err的值就等于参数的值
//const promisess = Promise.reject(new Promise((resolve) => resolve(555)))
// promisess.then(res => {
//   console.log("res:", res)
// }).catch(err => {
//   console.log("err:", err)
// })
const promisssse = Promise.reject({
  then(resolve) {
    resolve(112)
  }
})
promisssse.then(res => {
  console.log("res:", res)
}).catch(err => {
  console.log("err:", err)
})

//Promise.all()的用法
// const p1 = new Promise((resolve, reject) => {
//   setTimeout(() => {
//     resolve(11111)
//   }, 1000);
// })
// const p2 = new Promise((resolve, reject) => {
//   setTimeout(() => {
//     resolve(22222)
//   }, 2000);
// })
// const p3 = new Promise((resolve, reject) => {
//   setTimeout(() => {
//     resolve(33333)
//   }, 3000);
// })
//注意事项
//1. "aaaa"会转化成Promise.resolve("aaaa"), 数组中的值不是promise的全部会转成Promise.resolve(xxx)
//2. 所有的Promise都变成resolved时, p1、p2、p3的返回值和aaa组成一个数组传递给then函数中的res
//3. 只要p1、p2、p3之中有一个被rejected，此时第一个被reject的实例的返回值，会传递给catch函数中的err
// Promise.all([p2, p1, p3, "aaaa"]).then(res => {
//   console.log(res)
// }).catch(err => {
//   console.log("err:", err)
// })

//Promise.allSettled()的用法(实际项目中推荐使用这个不推荐使用all方法了)
// const p1 = new Promise((resolve, reject) => {
//   setTimeout(() => {
//     resolve(11111)
//   }, 1000);
// })

// const p2 = new Promise((resolve, reject) => {
//   setTimeout(() => {
//     reject(22222)
//   }, 2000);
// })

// const p3 = new Promise((resolve, reject) => {
//   setTimeout(() => {
//     resolve(33333)
//   }, 3000);
// })
//1. "aaaa"会转化成Promise.resolve("aaaa"), 数组中的值不是promise的全部会转成Promise.resolve(xxx)
//2. allSettled结果只会调用then()函数不会调用catch函数
// Promise.allSettled([p1, p2, p3,"aaa"]).then(res => {
//   console.log(res)
// }).catch(err => {
//   console.log(err)
// })

// Promise.race()的用法
// const p1 = new Promise((resolve, reject) => {
//   setTimeout(() => {
//     resolve(11111)
//   }, 3000);
// })

// const p2 = new Promise((resolve, reject) => {
//   setTimeout(() => {
//     reject(22222)
//   }, 5000);
// })

// const p3 = new Promise((resolve, reject) => {
//   setTimeout(() => {
//     resolve(33333)
//   }, 1000);
// })
//注意事项
//1. 数组中的值不是promise的全部会转成Promise.resolve(xxx)
//2. 只要p1、p2、p3之中有一个实例率先改变状态，那个率先改变的 Promise 实例的返回值(可能是resolved状态也可能是rejectd)是res或err
// Promise.race([p1, p2, p3]).then(res => {
//   console.log("res:", res)
// }).catch(err => {
//   console.log("err:", err)
// })

//Promise.any()的用法
// const p1 = new Promise((resolve, reject) => {
//   setTimeout(() => {
//     // resolve(11111)
//     reject(1111)
//   }, 1000);
// })

// const p2 = new Promise((resolve, reject) => {
//   setTimeout(() => {
//     //resolve(22222)
//     reject(22222)
//   }, 5000);
// })

// const p3 = new Promise((resolve, reject) => {
//   setTimeout(() => {
//     // resolve(33333)
//     reject(3333)
//   }, 3000);
// })
//注意事项
//1. 数组中的值不是promise的全部会转成Promise.resolve(xxx)
//2. 只要参数实例有一个变成fulfilled状态(优先等待第一个fulfilled状态)，包装实例就会变成fulfilled状态；如果所有参数实例都变成rejected状态，包装实例就会变成rejected状态
// Promise.any([p1, p2, p3]).then(res => {
//   console.log("res:", res)
// }).catch(err => {
//   console.log("err:", err,err.errors)//[ 1111, 22222, 3333 ]
// })


//老师实现一个Promise(用于参考)
// const PROMISE_STATUS_PENDING = 'pending'
// const PROMISE_STATUS_FULFILLED = 'fulfilled'
// const PROMISE_STATUS_REJECTED = 'rejected'

// // 工具函数
// function execFunctionWithCatchError(execFn, value, resolve, reject) {
//   try {
//     const result = execFn(value)
//     resolve(result)
//   } catch (err) {
//     reject(err)
//   }
// }

// class HYPromise {
//   constructor(executor) {
//     this.status = PROMISE_STATUS_PENDING
//     this.value = undefined
//     this.reason = undefined
//     this.onFulfilledFns = []
//     this.onRejectedFns = []

//     const resolve = (value) => {
//       if (this.status === PROMISE_STATUS_PENDING) {
//         // 添加微任务
//         queueMicrotask(() => {
//           if (this.status !== PROMISE_STATUS_PENDING) return
//           this.status = PROMISE_STATUS_FULFILLED
//           this.value = value
//           this.onFulfilledFns.forEach(fn => {
//             fn(this.value)
//           })
//         });
//       }
//     }

//     const reject = (reason) => {
//       if (this.status === PROMISE_STATUS_PENDING) {
//         // 添加微任务
//         queueMicrotask(() => {
//           if (this.status !== PROMISE_STATUS_PENDING) return
//           this.status = PROMISE_STATUS_REJECTED
//           this.reason = reason
//           this.onRejectedFns.forEach(fn => {
//             fn(this.reason)
//           })
//         })
//       }
//     }

//     try {
//       executor(resolve, reject)
//     } catch (err) {
//       reject(err)
//     }
//   }

//   then(onFulfilled, onRejected) {
//     const defaultOnRejected = err => { throw err }
//     onRejected = onRejected || defaultOnRejected

//     const defaultOnFulfilled = value => { return value }
//     onFulfilled = onFulfilled || defaultOnFulfilled

//     return new HYPromise((resolve, reject) => {
//       // 1.如果在then调用的时候, 状态已经确定下来
//       if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
//         execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
//       }
//       if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
//         execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
//       }

//       // 2.将成功回调和失败的回调放到数组中
//       if (this.status === PROMISE_STATUS_PENDING) {
//         if (onFulfilled) this.onFulfilledFns.push(() => {
//           execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
//         })
//         if (onRejected) this.onRejectedFns.push(() => {
//           execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
//         })
//       }
//     })
//   }

//   catch(onRejected) {
//     return this.then(undefined, onRejected)
//   }

//   // finally(onFinally) {
//   //   this.then(() => {
//   //     onFinally()
//   //   }, () => {
//   //     onFinally()
//   //   })
//   // }
//   finally(onFinally) {
//     this.then(onFinally, onFinally)
//   }

//   static resolve(value) {
//     return new HYPromise((resolve) => resolve(value))
//   }

//   static reject(reason) {
//     return new HYPromise((resolve, reject) => reject(reason))
//   }

//   static all(promises) {
//     // 问题关键: 什么时候要执行resolve, 什么时候要执行reject
//     return new HYPromise((resolve, reject) => {
//       const values = []
//       promises.forEach(promise => {
//         promise.then(res => {
//           values.push(res)
//           if (values.length === promises.length) {
//             resolve(values)
//           }
//         }, err => {
//           reject(err)
//         })
//       })
//     })
//   }

//   static allSettled(promises) {
//     return new HYPromise((resolve) => {
//       const results = []
//       promises.forEach(promise => {
//         promise.then(res => {
//           results.push({ status: PROMISE_STATUS_FULFILLED, value: res })
//           if (results.length === promises.length) {
//             resolve(results)
//           }
//         }, err => {
//           results.push({ status: PROMISE_STATUS_REJECTED, value: err })
//           if (results.length === promises.length) {
//             resolve(results)
//           }
//         })
//       })
//     })
//   }

//   static race(promises) {
//     return new HYPromise((resolve, reject) => {
//       promises.forEach(promise => {
//         // promise.then(res => {
//         //   resolve(res)
//         // }, err => {
//         //   reject(err)
//         // })
//         promise.then(resolve, reject)
//       })
//     })
//   }

//   static any(promises) {
//     // resolve必须等到有一个成功的结果
//     // reject所有的都失败才执行reject
//     const reasons = []
//     return new HYPromise((resolve, reject) => {
//       promises.forEach(promise => {
//         promise.then(resolve, err => {
//           reasons.push(err)
//           if (reasons.length === promises.length) {
//             reject(new AggregateError(reasons))
//           }
//         })
//       })
//     })
//   }
// }

// const promise = new HYPromise((resolve, reject) => {
//   console.log("状态pending")
//   resolve(1111) // resolved/fulfilled
//   // reject(2222)
// })
// // 调用then方法多次调用
// promise.then(res => {
//   console.log("res1:", res)
//   return "aaaaa"
// }).then(res => {
//   console.log("res2:", res)
//   return "bbbbbvvv"
// }).catch(err => {
//   console.log("err:", err)
// }).then(res => {
//   console.log("res3:", res)
// }).finally(() => {
//   console.log("finally")
// })

// HYPromise.resolve("Hello World").then(res => {
//   console.log("res:", res)
// })

// HYPromise.reject("Error Message").catch(err => {
//   console.log("err:", err)
// })

// const p1 = new Promise((resolve, reject) => {
//   setTimeout(() => { reject(1111) }, 3000)
// })
// const p2 = new Promise((resolve, reject) => {
//   setTimeout(() => { reject(2222) }, 2000)
// })
// const p3 = new Promise((resolve, reject) => {
//   setTimeout(() => { reject(3333) }, 3000)
// })

// // HYPromise.all([p1, p2, p3]).then(res => {
// //   console.log(res)
// // }).catch(err => {
// //   console.log(err)
// // })

// HYPromise.allSettled([p1, p2, p3]).then(res => {
//   console.log(res)
// })

// // HYPromise.race([p1, p2, p3]).then(res => {
// //   console.log("res:", res)
// // }).catch(err => {
// //   console.log("err:", err)
// // })
// HYPromise.any([p1, p2, p3]).then(res => {
//   console.log("res:", res)
// }).catch(err => {
//   console.log("err:", err.errors)
// })


//自己手写实现一个Promise(参考codewhy老师视频20和21节课)
const commonFn = (fn, value, resolve, reject) => {
  try {
    const result = fn(value)
    resolve(result)

  } catch (err) {
    reject(err)
  }
}
class HYMPromise {
  constructor(extera) {
    this.state = "pending"
    this.onFullfieldfns = []
    this.onRejectedfns = []
    const resolve = (value) => {
      queueMicrotask(() => {
        if (this.state == "pending") {
          this.state = "resolved"
          this.value = value
          this.onFullfieldfns.forEach(fn => {
            fn()
          })
        }
      })
    }
    const reject = (reason) => {
      queueMicrotask(() => {
        if (this.state == "pending") {
          this.state = "rejected"
          this.reason = reason
          this.onRejectedfns.forEach(fn => {
            fn()
          })
        }
      })
    }
    try {
      extera(resolve, reject)
    } catch (err) {
      reject(err)
    }
  }
  then(onFulfilled, onRejected) {
    onFulfilled = onFulfilled || (value => value)
    onRejected = onRejected || (err => { throw err })
    return new HYMPromise((resolve, reject) => {
      if (this.state == "resolved") {
        commonFn(onFulfilled, this.value, resolve, reject)
      }
      if (this.state == "rejected") {
        commonFn(onRejected, this.reason, resolve, reject)
      }
      if (this.state == "pending") {
        this.onFullfieldfns.push(() => {
          commonFn(onFulfilled, this.value, resolve, reject)
        })
        this.onRejectedfns.push(() => {
          commonFn(onRejected, this.reason, resolve, reject)
        })
      }
    })
  }
  catch(onRejected) {
    return this.then(undefined, onRejected)
  }
  finally(back) {
    this.then(back, back)
  }
  static resolve(value) {
    return new Promise((resolve) => {
      resolve(value)
    })
  }
  static reject(value) {
    return new Promise((resolve, reject) => {
      reject(value)
    })
  }
  static all(promises) {
    let resolves = []
    return new Promise((resolve, reject) => {
      for (let promise of promises) {
        promise.then(res => {
          resolves.push(res)
          if (resolves.length === promises.length) resolve(resolves)
        }, reject)
      }
    })
  }
  static allSettled(promises) {
    let resolves = []
    return new Promise((resolve, reject) => {
      promises.forEach((promise) => {
        promise.then((res) => {
          resolves.push({
            status: "fulfilled",
            value: res
          })
          if (resolves.length === promises.length) resolve(resolves)
        }, (err) => {
          resolves.push({
            status: "rejected",
            reason: err
          })
          if (resolves.length === promises.length) resolve(resolves)
        })
      })
    })
  }
  static race(promises) {
    return new Promise((resolve, reject) => {
      promises.forEach((promise) => {
        promise.then(resolve, reject)
      })
    })
  }
  static any(promises) {
    let rejects = []
    return new Promise((resolve, reject) => {
      promises.forEach((promise) => {
        promise.then(resolve, (err) => {
          rejects.push(err)
          if (rejects.length === promises.length) reject(new AggregateError(rejects))
        })
      })
    })
  }
}
const p11 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve(1111) }, 300)
})
const p22 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve(2222) }, 200)
})
const p33 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve(3333) }, 100)
})

function sleeps(delay, number) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(number);
      resolve(number)
    }, delay);
  })
}
async function hydtf(sss) {
  for (let item of sss) {
    const res = await item()
    console.log(res);
    // item.then(res => {
    //   console.log(res);
    // })
  }  //继发执行
  // sss.forEach(async v => {
  //   const res = await v()
  //   console.log(res);
  //   // v.then(res => {
  //   //   console.log(res);
  //   // })
  // }) //forEach，forEach中加async await, 不加async await的for of 这3中情况都是并发执行(同时执行)，加async await的for of是继发执行(按先后顺序执行)
}
hydtf([()=>sleeps(300,1111), ()=>sleeps(200,2222), ()=>sleeps(100,3333)])
Promise.all([p11, p22, p33]).then(res => {
  console.log(res, 6000)
}).catch(err => {
  console.log(err)
})


// 宏任务微任务执行顺序
// fn1，fn2中async/await转换Promise就是fn3 这种带有async/await的函数只有转换成promise的形式才能分析执行顺序
async function fn1(){
  console.log('test1')
  console.log(await test2())
  console.log('test2')
}
async function fn2(){
  console.log('test3')
  return await 'return value'
}
// fn1，fn2中async/await转换Promise就是fn3 这种带有async/await的函数只有转换成promise的形式才能分析执行顺序

async function fn3(){
  console.log('test1')
   new Promise(resolve1=>{
      Promise.resolve('return value').then(res=>{
              console.log('res')  
              resolve1(res)
      })
  }).then(res1=>{
    console.log('return value')
     console.log('test2')
  })
}
fn3()
new Promise((resolve,reject)=>{
 resolve()
}).then(()=>{
console.log('promise2')
})
test1
res
promise2
test2
// fn1，fn2中async/await转换Promise就是fn3 这种带有async/await的函数只有转换成promise的形式才能分析执行顺序

setTimeout(function () {
  console.log("setTimeout");
   new Promise(function (resolve) {
    console.log("promise1");
    resolve();
  }).then(function () {
    console.log("promise2");
  });
}, 0);

setTimeout(function () {
  console.log("setTimeout2");
   new Promise(function (resolve) {
    console.log("promise3");
    resolve();
  }).then(function () {
    console.log("promise4");
  });
}, 0);
setTimeout
promise1
promise2
setTimeout2
promise3
promise4
// 宏任务微任务执行顺序


//迭代器(了解即可)
//迭代器的定义是帮助我们对某个数据结构遍历的对象
//迭代器满足的条件
//1.需要有一个next函数(无参数或者一个参数),返回一个应当拥有以下两个属性的对象
//2.done和value属性，如果迭代器可以产生序列中的下一个值，done则为 false,value为数据结构中对应的值,
//如果迭代器已将序列迭代完毕，done则为 true,value为undefined
//forEach是可迭代对象iterable内置的方法，也可以用 for of  但是forEach更简单推荐用forEach，但是如果要在循环中使用await 不推荐用forEach推荐用for of
//封装一个迭代器函数
// function createArrayIterator(arr) {
//   let index = 0
//   return {
//     next: function () {
//       if (index < arr.length) {
//         return { done: false, value: arr[index++] }
//       } else {
//         return { done: true, value: undefined }
//       }
//     }
//   }
// }
// const strings = ["abc", "cba", "nba"]
// const nums = [10, 22, 33, 12]

// const namesIterator = createArrayIterator(strings)
// console.log(namesIterator.next())
// console.log(namesIterator.next())
// console.log(namesIterator.next())
// console.log(namesIterator.next())

// const numsIterator = createArrayIterator(nums)
// console.log(numsIterator.next())
// console.log(numsIterator.next())
// console.log(numsIterator.next())
// console.log(numsIterator.next())
// console.log(numsIterator.next())

// 创建一个可迭代对象来访问数组
//可迭代对象满足的条件
//1.可迭代对象中有Symbol.iterator属性，它的值是一个函数，这个函数的返回值是一个迭代器
// const iterableObj = {
//   names: ["abc", "cba", "nba"],
//   [Symbol.iterator]: function () {
//     let index = 0
//     return {
//       next: () => {
//         if (index < this.names.length) {
//           return { done: false, value: this.names[index++] }
//         } else {
//           return { done: true, value: undefined }
//         }
//       }
//     }
//   }
// }

// iterableObj对象就是一个可迭代对象
// console.log(iterableObj[Symbol.iterator])

// 1.第一次调用iterableObj[Symbol.iterator]函数
// const iterator = iterableObj[Symbol.iterator]()
// console.log(iterator.next())
// console.log(iterator.next())
// console.log(iterator.next())
// console.log(iterator.next())

// // 2.第二次调用iterableObj[Symbol.iterator]函数
// const iterator1 = iterableObj[Symbol.iterator]()
// console.log(iterator1.next())
// console.log(iterator1.next())
// console.log(iterator1.next())
// console.log(iterator1.next())

// 3.for...of可以遍历的东西必须是一个可迭代对象
// const obj = {
//   name: "why",
//   age: 18
// }

// for (const item of obj) {
//   console.log(item)
// }
// 我们平时创建的很多原生对象已经实现了可迭代协议，会生成一个迭代器(Iterator)对象的：String、Array、Set、Map、arguments对象、NodeList集合(querySelectorAll生成的就是NodeList)，对象不是一个可迭代对象(上面的obj)


//生成器(了解即可)
//生成器(本质是一个特殊的迭代器)定义是ES6中新增的一种函数控制、使用的方案，它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等
// 当遇到yield时候是暂停函数的执行
// 当遇到return时候生成器就停止执行
// function* foo() {
//   console.log("函数开始执行~")

//   const value1 = 100
//   console.log("第一段代码:", value1)
//   yield value1

//   const value2 = 200
//   console.log("第二段代码:", value2)
//   yield value2

//   const value3 = 300
//   console.log("第三段代码:", value3)
//   yield value3

//   console.log("函数执行结束~")
//   return "123"
// }
// // generator本质上是一个特殊的iterator
// const generator1 = foo()
// console.log("返回值1:", generator1.next())
// console.log("返回值2:", generator1.next())
// console.log("返回值3:", generator1.next())
// console.log("返回值4:", generator1.next())

//生成器上的next方法可以传递参数
// function* foo(num) {
//   console.log("函数开始执行~")

//   const value1 = 100 * num
//   console.log("第一段代码:", value1)
//   const n = yield value1

//   const value2 = 200 * n
//   console.log("第二段代码:", value2)
//   const count = yield value2

//   const value3 = 300 * count
//   console.log("第三段代码:", value3)
//   const bum = yield value3

//   console.log("返回值:", bum)
//   console.log("函数执行结束~")
//   return "123"
// }
// // 生成器上的next方法可以传递参数
// const generator = foo(5)
// console.log(generator.next())
// // 第二段代码, 第二次调用next的时候执行的
// console.log(generator.next(10))
// console.log(generator.next(25))
// console.log(generator.next(30))


//生成器替代迭代器使用
// function createArrayIterator(arr) {
//   let index = 0
//   return {
//     next: function () {
//       if (index < arr.length) {
//         return { done: false, value: arr[index++] }
//       } else {
//         return { done: true, value: undefined }
//       }
//     }
//   }
// }
//1.下面生成器函数替代上面返回迭代器的函数
// function* createArrayIterator(arr) {
//   // 1.第一种写法
//   // yield "abc" // { done: false, value: "abc" }
//   // yield "cba" // { done: false, value: "abc" }
//   // yield "nba" // { done: false, value: "abc" }

//   // 2.第二种写法
//   // for (const item of arr) {
//   //   yield item
//   // }

//   // 3.第三种写法 yield* (yield*是第二种写法的语法糖)
//   yield* arr
// }
// const contents = ["abc", "cba", "nba"]
// const namesIterator = createArrayIterator(contents)

// console.log(namesIterator.next())
// console.log(namesIterator.next())
// console.log(namesIterator.next())
// console.log(namesIterator.next())

// 2.创建一个生成器函数, 这个函数可以迭代一个范围内的数字
// function* createRangeIterator(start, end) {
//   while (start < end) {
//     yield start++
//   }

//   // return {
//   //   next: function () {
//   //     if (start < end) {
//   //       return { done: false, value: start++ }
//   //     } else {
//   //       return { done: true, value: undefined }
//   //     }
//   //   }
//   // }
// }
// const rangeIterator = createRangeIterator(10, 20)
// console.log(rangeIterator.next())
// console.log(rangeIterator.next())
// console.log(rangeIterator.next())
// console.log(rangeIterator.next())
// console.log(rangeIterator.next())

// 3.class案例
// class Classroom {
//   constructor(address, name, students) {
//     this.address = address
//     this.name = name
//     this.students = students
//   }

//   entry(newStudent) {
//     this.students.push(newStudent)
//   }

//   foo = () => {
//     console.log("foo function")
//   }

//   // [Symbol.iterator] = function*() {
//   //   yield* this.students
//   // }

//   *[Symbol.iterator]() {
//     yield* this.students
//   }
// }

// const classroom = new Classroom("3幢", "1102", ["abc", "cba"])
// for (const item of classroom) {
//   console.log(item)
// }


//异步代码的处理方案(async await之前的使用方式)
// function requestData(url) {
//   // 异步请求的代码会被放入到executor中
//   return new Promise((resolve, reject) => {
//     // 模拟网络请求
//     setTimeout(() => {
//       // 拿到请求的结果
//       resolve(url)
//     }, 2000);
//   })
// }
// 需求: 
// 1> url: why -> res: why
// 2> url: res + "aaa" -> res: whyaaa
// 3> url: res + "bbb" => res: whyaaabbb

// 1.第一种方案: 多次回调
// 回调地狱
// requestData("why").then(res => {
//   requestData(res + "aaa").then(res => {
//     requestData(res + "bbb").then(res => {
//       console.log(res)
//     })
//   })
// })

// 2.第二种方案: Promise中then的返回值来解决
// requestData("why").then(res => {
//   return requestData(res + "aaa")
// }).then(res => {
//   return requestData(res + "bbb")
// }).then(res => {
//   console.log(res)
// })

// 3.第三种方案: Promise + generator实现
// function* getData() {
//   const res1 = yield requestData("why")
//   const res2 = yield requestData(res1 + "aaa")
//   const res3 = yield requestData(res2 + "bbb")
//   const res4 = yield requestData(res3 + "ccc")
//   console.log(res4)
// }

// // 1> 手动执行生成器函数
// const generator = getData()
// generator.next().value.then(res => {
//   generator.next(res).value.then(res => {
//     generator.next(res).value.then(res => {
//       generator.next(res)
//     })
//   })
// })

// 2> 自己封装了一个自动执行的函数
// function execGenerator(genFn) {
//   const generator = genFn()
//   function exec(res) {
//     const result = generator.next(res)
//     if (result.done) {
//       return result.value
//     }
//     result.value.then(res => {
//       exec(res)
//     })
//   }
//   exec()
// }
// execGenerator(getData)

// 3> 第三方包co自动执行
// TJ: co/n(nvm)/commander(coderwhy/vue cli)/express/koa(egg)
// const co = require('co')
// co(getData)

// 4.第四种方案: async/await(本质是Promise + generator(生成器)的语法糖)
// async function getData() {
//   const res1 = await requestData("why")
//   const res2 = await requestData(res1 + "aaa")
//   const res3 = await requestData(res2 + "bbb")
//   const res4 = await requestData(res3 + "ccc")
//   console.log(res4)
// }
// getData()


//async await用法
//async用法
/*async函数是一个异步函数(函数内部没有特别之处(比如await)的话和同步函数执行流程是一样的)，
如果async函数内部抛出错误(throw xxx)或return Promise.reject(xxx)或return {
    then: function (resolve, reject) {
      reject("hahahah")
    }
  }时,
async函数的返回值是Promise.reject(xxx),如果async函数内部的其他代码写法，返回值都是Promise.resolve(xxx)*/

// async function ss() {
//   console.log("内部的代码执行1")
//   return 111
// }

// console.log("script start")
// console.log(ss());//Promise {<fulfilled>: 111} 如果ss函数中没有返回值,结果Promise {<fulfilled>: undefined}
// console.log("script end")

// ss().then(res => {
//   console.log("res", res);//111
// }).catch(err => {
//   console.log("err", err);
// })

//async函数和普通函数的区别
//1.返回值的区别
async function foo() {
  // console.log("foo function start~")

  // console.log("中间代码~")

  // console.log("foo function end~")

  //1.返回一个值

  //2.返回thenable
  // return {
  //   then: function (resolve, reject) {
  //     reject("hahahah")
  //   }
  // }

  // 3.返回Promise
  // return new Promise((resolve, reject) => {
  //   setTimeout(() => {
  //     reject("hehehehe")
  //   }, 2000)
  // })
}

// // 异步函数的返回值一定是一个Promise
// const promise = foo()
// console.log(promise, "58666")
// promise.then(res => {
//   console.log("promise then function exec:", res)
// }).catch(err => {
//   console.log("err:", err)
// })

//2.抛出异常的区别
//普通函数抛出异常没有try catch捕获的话程序会崩掉,后面代码停止执行，有的话正常执行
// function foo() {
//   console.log("foo function start~")

//   throw new Error("error message")
//   console.log("foo function end~") //这行代码不会执行
// }

// foo()
// console.log("后续还有代码~~~~~") //同步函数这行代码不会执行

//异步函数抛出异常有没有try catch捕获程序都能正常执行
// async function foo() {
//   console.log("foo function start~")

//   // 异步函数中的异常, 会被作为异步函数返回的Promise的reject值的
//   throw new Error("error message")

//   console.log("foo function end~")//这行代码不会执行
// }
// // // 异步函数的返回值一定是一个Promise
// foo()
// // foo().catch(err => {
// //   console.log("coderwhy err:", err) //error message   会捕获异步函数中的错误
// // })
// console.log("后续还有代码~~~~~") //异步函数这行代码会执行

//async await一起使用
// 1.await跟上表达式
// function requestData() {
//   return new Promise((resolve, reject) => {
//     setTimeout(() => {
//       // resolve(222)
//       reject(1111)
//     }, 2000);
//   })
// }

// async function foo() {
/*requestData()的返回值如果是是Promise.resolve(xxx),那么res1=xxx,
后面的console.log(1),console.log(2),console.log(3)代码
相当于这样写Promise.resolve(xxx).then(res=>{console.log(1),console.log(2),console.log(3)})并且依次执行
如果requestData()的返回值是Promise.reject(xxx),那么console.log(1),console.log(2),console.log(3)就不在执行了
xxx是foo()函数返回的promise的reject函数的实际参数值,也相当于是Promise.reject(xxx)*/
//   const res1 = await requestData()
//   console.log("1", res1)
//   console.log("2")
//   console.log("3")

//   const res2 = await requestData()
//   console.log("res2后面的代码", res2)
// }

// 2.跟上其他的值
// async function foo() {
//   // const res1 = await 123
//   // const res1 = await {
//   //   then: function(resolve, reject) {
//   //     resolve("abc")
//   //   }
//   // }
//   const res1 = await new Promise((resolve) => {
//     resolve("why")
//   })
//   console.log("res1:", res1)
// }

// // 3.reject值
// async function foo() {
//   const res1 = await requestData()
//   console.log("res1:", res1)
// }

// foo().catch(err => {
//   console.log("err:", err)
// })


//浏览器的宏任务和微任务队列

//宏任务和微任务队列执行顺序：同步代码优先执行完，然后微任务队列中的代码执行完，最后在执行宏任务队列中的代码

//宏任务队列有哪些回调函数？
// 1.setTimeout,setInterval中的回调函数
// 2.ajax的回调函数
// 3.DOM事件(点击,移动，键盘按下,抬起等等事件)对应的回调函数
// 4.UI渲染完之后的回调函数

//微任务队列有哪些回调函数？
// queueMicrotask(() => {
//   console.log("queueMicrotask")
// })

// Promise.resolve().then(() => {
//   console.log("Promise then")
// })
//1.queueMicrotask中的回调函数
//2.Promise.then或Promise.catch中的回调函数

//node的事件循环在24节课后半部分


/**
 * 如果我们有一个函数, 在调用这个函数时, 如果出现了错误, 那么我们应该是去修复这个错误.
 */
// function sum(num1, num2) {
//   // 当传入的参数的类型不正确时, 应该告知调用者一个错误
//   if (typeof num1 !== "number" || typeof num2 !== "number") {
//     // return undefined
//     throw "parameters is error type~"
//   }

//   return num1 + num2
// }

// // 调用者(如果没有对错误进行处理, 那么程序会直接终止)
//  console.log(sum({ name: "why" }, true))
// //sum({ name: "why" }, true)
// // try {
// //   sum(null, 30)
// // } catch (e) {
// //   console.log(e, "633595")
// // }finally {
//   console.log("finally代码执行~, close操作")
// }
// console.log("后续的代码会继续运行~")


//序列化方法stringify细节
// const obj = {
//   name: "why",
//   age: 18,
//   friends: {
//     name: "kobe"
//   },
//   hobbies: ["篮球", "足球"],
//   foo: function() {
//     console.log("foo function")
//   }
//   // toJSON: function() {
//   //   return "123456"
//   // }
// }
// // 需求: 将上面的对象转成JSON字符串
// // 1.直接转化(foo函数会被过滤掉，因为JSON格式数据不支持函数)
// const jsonString1 = JSON.stringify(obj)
// console.log(jsonString1)

// // 2.stringify第二个参数replacer
// // 2.1. 传入数组: 设定哪些是需要转换
// const jsonString2 = JSON.stringify(obj, ["name", "friends"])
// console.log(jsonString2)

// // 2.2. 传入回调函数:
// const jsonString3 = JSON.stringify(obj, (key, value) => {
//   if (key === "age") {
//     return value + 1
//   }
//   return value
// })
// console.log(jsonString3)

// // 3.stringify第三参数 space
// const jsonString4 = JSON.stringify(obj, null, "---")
// console.log(jsonString4)

// // 4.如果obj对象中有toJSON方法 结果是toJSON函数的返回值 123456

// //JSON字符串解析parse细节传入第二个回调函数参数
// const JSONString = '{"name":"why","age":19,"friends":{"name":"kobe"},"hobbies":["篮球","足球"]}'
// const info = JSON.parse(JSONString, (key, value) => {
//   if (key === "age") {
//     return value - 1
//   }
//   return value
// })
// console.log(info)



//Storage常见的属性和方法
// 1.setItem
// localStorage.setItem("name", "coderwhy")
// localStorage.setItem("age", 18)

// // 2.length
// console.log(localStorage.length)
// for (let i = 0; i < localStorage.length; i++) {
//   const key = localStorage.key(i)
//   console.log(localStorage.getItem(key))
// }

// // 3.key方法
// console.log(localStorage.key(0))

// // 4.getItem(key)
// console.log(localStorage.getItem("age"))

// // 5.removeItem
// localStorage.removeItem("age")

// // 6.clear方法
// localStorage.clear()


//认识BOM
//我们可以将BOM看成是连接JavaScript脚本与浏览器窗口的桥梁(BOM是浏览器提供的,方便js操作浏览器)
// BOM主要包括一下的对象模型：
// window(是JS的最顶层对象，其他的BOM对象都是window对象的属性)包括全局属性、方法，控制浏览器窗口相关的属性、方法
// location：浏览器连接到的对象的位置（URL）；
// history：操作浏览器的历史；
// document：当前窗口操作文档的对象；

// window对象在浏览器中有两个身份：
// 身份一：全局对象。
// 我们知道ECMAScript其实是有一个全局对象的，这个全局对象在Node中是global；
// 在浏览器中就是window对象；
// 身份二：浏览器窗口对象。
// 作为浏览器窗口时，提供了对浏览器操作的相关的API；


//防抖函数和节流函数的实现
//防抖函数
// const iptEl = document.getElementById("ipt")
// let counter = 0
// const inputChange = function (event) {
//   console.log(`发送了第${++counter}次网络请求`, this, event)
//   return "aaaaaaaaaaaa"
// }
// const debounceChange = debounce(inputChange, 3000, (res) => {
//   console.log("拿到真正执行函数的返回值:", res)
// })
// iptEl.oninput = debounceChange
// //取消功能
// const cancelBtn = document.querySelector("#cancel")
// cancelBtn.onclick = function () {
//   debounceChange.cancel()
// }
// //immediate第一次是否立即执行
// function debounce(fn, delay, handleCallback, immediate = false) {
//   // 1.定义一个定时器, 保存上一次的定时器
//   let timer = null
//   let isInvoke = true
//   // 2.真正执行的函数
//   const _debounce = function (...args) {
//     // 判断是否需要立即执行
//     if (immediate && isInvoke) {
//       const res = fn.apply(this, args)
//       if (handleCallback && typeof handleCallback === 'function') handleCallback(res)
//       isInvoke = false
//     } else {
//       // 取消上一次的定时器
//       if (timer) clearTimeout(timer)
//       // 延迟执行
//       timer = setTimeout(() => {
//         // 外部传入的真正要执行的函数
//         const res = fn.apply(this, args)
//         if (handleCallback && typeof handleCallback === 'function') handleCallback(res)
//         isInvoke = true
//         timer = null //不写的话timer会被赋值成一个随机数1，2，3，4...
//       }, delay)
//     }
//   }

//   // 封装取消功能
//   _debounce.cancel = function () {
//     if (timer) clearTimeout(timer)
//     timer = null
//     isInvoke = true
//   }

//   return _debounce
// }
//防抖简易版(单位时间内事件多次触发只会执行最后一次，前面的触发的定时器都被取消了)
function debounce(fn) {
  let timer = null
  return function (...arg) {
    clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, arg)
      timer = null
    }, 1000)
  }
}

//节流
// const iptEl = document.getElementById("ipt")
// let counter = 0

// const inputChange = function (event) {
//   console.log(`发送了第${++counter}次网络请求`, this, event)
//   return "aaaaaaaaaaaa"
// }
// const debounceChange = throttle(inputChange, 2000, {
//   leading: true,//第一次是否执行
//   trailing: true,//最后一次是否执行
//   handleCallback: function (res) {
//     console.log("callback", res);
//   }
// })
// iptEl.oninput = debounceChange
// // //取消功能
// const cancelBtn = document.querySelector("#cancel")
// cancelBtn.onclick = function () {
//   debounceChange.cancel()
// }
// function throttle(fn, interval, { leading = false, trailing = false, handleCallback } = {}) {
//   // 1.记录上一次的开始时间
//   let lastTime = 0
//   let timer = null
//   // 2.事件触发时, 真正执行的函数
//   const _throttle = function (...args) {

//     // 2.1.获取当前事件触发时的时间
//     const nowTime = new Date().getTime()
//     if (!lastTime && !leading) lastTime = nowTime

//     // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数
//     const remainTime = interval - (nowTime - lastTime)
//     if (remainTime <= 0) {
//       if (timer) {
//         clearTimeout(timer)
//         timer = null
//       }
//       // 2.3.真正触发函数
//       const res = fn.apply(this, args)
//       if (handleCallback && typeof handleCallback === 'function') handleCallback(res)
//       // 2.4.保留上次触发的时间
//       lastTime = nowTime
//     } else {
//       if (trailing && !timer) {
//         timer = setTimeout(() => {
//           const res = fn.apply(this, args)
//           if (handleCallback && typeof handleCallback === 'function') handleCallback(res)
//           lastTime = !leading ? 0 : new Date().getTime()
//           timer = null
//         }, remainTime)
//       }
//     }
//   }
//   _throttle.cancel = function () {
//     if (timer) clearTimeout(timer)
//     timer = null
//     lastTime = 0
//   }
//   return _throttle
// }
//节流简易版(单位时间内只会触发第一次,后面触发的事件都不会走定时器了)
function throttle(fn) {
  let isTimerRun = false
  return function (...args) {
    if (isTimerRun) return
    isTimerRun = true
    setTimeout(() => {
      fn.apply(this, args)
      isTimerRun = false
    }, 1000)
  }
}


//函数对象的用法
// let obj11 = {
//   name: "why",
//   tem: () => {

//   },
//   array: [11, 2, 3]
// }
// for (let key in obj11.array) {
//   console.log(key, "9652");
// }

// let obj22 = { ...obj11 }
// obj11.tem.age = 100
// console.log(obj22.tem.age, obj11.tem === obj22.tem, "12356")

// let qwes = new Function("let s=100;console.log(111);return 555")
// console.log(qwes, qwes(), "963");


//把一个对象中属性的值合并到另外一个对象中
// const obj = {
//   name: "wyet",
//   age: 20,
//   tem: {
//     title: "teyee",
//     telrm: "jduyrh",
//     uyrtsd: "50"
//   },
//   [Symbol()]: 100
// }
const isObj = (value) => typeof value === "object" && value !== null
function margeObj(state, objOrfn) {
  const keys = Reflect.ownKeys(objOrfn)
  for (const key of keys) {
    let oldVal = state[key]
    let newVal = objOrfn[key]
    if (isObj(oldVal) && isObj(newVal)) {
      state[key] = margeObj(oldVal, newVal)
    } else {
      state[key] = newVal
    }
  }
  return state
}
// //Reflect.ownKeys(obj) 可以获取所有的属性，Object.keys(obj)和for...in不能获取为symbol值的属性
// console.log(Reflect.ownKeys(obj), margeObj(obj, {
//   tes: 200,
//   [Symbol()]: 900,
//   tem: {
//     title: "er",
//     wye: 500
//   }
// }));


//深拷贝
const deepClone = (obj, map = new WeakMap()) => {
  if (obj instanceof Set) {
    return new Set(obj)
  }
  if (obj instanceof Map) {
    return new Map(obj)
  }
  //WeakMap 和 WeakSet 最明显的局限性就是不能迭代，并且无法获取所有当前内容
  if (obj === null || typeof obj == "symbol" || typeof obj == "function" || typeof obj !== "object" || obj instanceof WeakSet || obj instanceof WeakMap) {
    return obj
  }
  if (map.has(obj)) {
    return map.get(obj)
  }
  const newObj = Array.isArray(obj) ? [] : {}
  map.set(obj, newObj)  //放在这里是对的，解决循环引用的问题
  //Reflect.ownKeys(obj) 可以获取所有的属性，Object.keys(obj)和for...in不能获取为symbol值的属性     
  const keys = Reflect.ownKeys(obj)
  for (const key of keys) {
    newObj[key] = deepClone(obj[key], map)
  }
  // map.set(obj, newObj) //放在这里会导致循环引用的对象无法被正确拷贝,会爆栈
  return newObj
}
let sdw = { a: 1};
sdw.b = sdw
const qww=deepClone(sdw)
console.log(qww)
// const s1 = Symbol("aaa")
// const s2 = Symbol("bbb")
// const oldObject = {
//   name: "why",
//   friend: {
//     name: "kou",
//     age: 18
//   },
//   array: [1, 2, 3],
//   foo: function () {
//     console.log("函数");
//   },
//   [s1]: "abc",
//   s2: s2,
//   set: new Set([1, 2, 3]),
//   map: new Map([["a", "abc"], ["b", "cba"]])
// }

// oldObject.info = oldObject

// const newObj = deepClone(oldObject)
// console.log(newObj == oldObject, "00000")
// oldObject.name = "whys"
// oldObject.friend.age = 2000
// oldObject.array[0] = 300
// oldObject.set.add(111)
// oldObject.map.set("qqq", "wwww")
// console.log(oldObject, newObj, "11111")


//WeakMap弱引用的用法
// let asd = { name: "why" }
// let qwe = {}
// const weakmap = new WeakMap()
// const map = new Map()
// //weakmap.set(asd, "对象内容").set(qwe, "空对象")
// map.set(asd, "对象内容").set(qwe, "空对象")
// console.log(map, "8966");
// asd = null
// setTimeout(() => {
//   console.log(map, "123");
// }, 8000)


//单例模式(一个类只有一个实例对象)
const Person = (() => {
  function Animal() { }
  let instance = null
  return function () {
    if (!instance) instance = new Animal()
    return instance
  }
})()
const p1 = Person()
const p2 = Person()
console.log(p1 == p2)//true

//观察者模式
class Dep {
  constructor() {
    this.deps = []
  }
  add(watcher) {
    if (!this.deps.includes(watcher)) {
      this.deps.push(watcher)
    }
  }
  remove(watcher) {
    if (this.deps.includes(watcher)) {
      let index = this.deps.indexOf(watcher)
      this.deps.splice(index, 1)
    }
  }
  notify() {
    this.deps.forEach(item => {
      if (item.update) {
        item.update()
      }
    })
  }
}
class Watcher {
  constructor() { }
  update() {
    console.log("更新我");
  }
}
const dep = new Dep()
const watcher1 = new Watcher()
const watcher2 = new Watcher()
dep.add(watcher1)
dep.add(watcher2)
dep.notify()

//发布订阅模式
//vue中的事件总线(mini-mitt)
class JHmitt {
  constructor() {
    this.obj = {}
  }
  on(name, callback, thisAge) {
    if (!(this.obj[name])) {
      this.obj[name] = []
    }
    this.obj[name].push({
      callback,
      thisAge
    })
  }
  emit(name, ...args) {
    const headers = this.obj[name]
    if (!headers) return
    headers.forEach(item => {
      item.callback.apply(item.thisAge, args)
    })
  }
  off(name, callback) {
    const headers = this.obj[name]
    if (!headers) return
    headers.forEach((item, index) => {
      if (callback === undefined) {
        this.obj[name] = []
      } else {
        if (item.callback === callback) {
          headers.splice(index, 1)
        }
      }
    })
  }
}
// const newJHmitt = new JHmitt()
// const callback = function (...args) {
//   console.log(args, 123);//this在浏览器中是window
// }
// newJHmitt.on("abc", callback)
// newJHmitt.on("abc", function () {
//   console.log("监听abc", this.name);
// }, { name: "why" })
// newJHmitt.emit("abc", 456)
// newJHmitt.off("abc")
// newJHmitt.emit("abc",789)

//自己手写发布订阅模式
class mitts {
  constructor() {
    this.obj = {};
  }
  //订阅
  on(name, callback, thisArg) {
    if (!this.obj[name]) {
      this.obj[name] = []
    }
    this.obj[name].push({
      callback,
      thisArg
    })
  }
  //发布
  emit(name, ...arg) {
    if (this.obj[name]) {
      this.obj[name].forEach(item => {
        item.callback.apply(item.thisArg, arg)
      })
    }
  }
  off(name, callback) {
    if (callback) {
      let index = this.obj[name].findIndex(item => callback === item.callback)
      if (index >= 0) {
        this.obj[name].splice(index, 1)
      }
    } else {
      this.obj[name] = []
    }
  }
}


//实现原生reduce方法
// Array.prototype.reduce = function (callback,pre) {
//   for (let i = 0; i < this.length; i++){
//     if (!pre) {
//       pre = callback(this[i], this[i + 1], i + 1, this)
//       i++
//     } else {
//       pre = callback(pre, this[i], i, this)
//     }
//   }
//   return pre
// }
// let total = [1, 2, 3].reduce(function (pre,current) {
//   return pre + current
// })
// console.log(total, "56633");


//  for...of和async await结合用法
function sleep(value) {
  return new Promise((resolve, reject) => {
    if (value < 3) {
      resolve(100)
    } else {
      reject(500)
    }
  })
}

async function chainAnimationsAsync() {
  const telist = []
  try {
    for (let value of [1, 2, 3, 4, 5]) {
      //下面这段代码失败就会跳出整个循环，然后执行catch方法
      res = await sleep(value);
      telist.push(res)
    }
  } catch (err) {
    telist.push(err)
    console.log(err, 9100);
  }
  return telist
}
chainAnimationsAsync().then((res) => {
  console.log(res, 9200);
}, (err) => {
  console.log(err, 400);
}); //500 9100  [ 100, 100, 500 ] 9200

const p11 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(1)
  }, 1000)
})
const p22 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(2)
  }, 2000)
})
const p33 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(3)
  }, 3000)
})
const pss = Promise.any([p11, p22, p33]);
pss.then((res) => {
  console.log(res, 100);
}).catch((err) => {
  console.log(err, 200);
})

let dgf = null
const resd = async () => {
  const res = await new Promise((resolve) => {
    dgf = resolve
  })
  console.log(res);

}
resd()
setTimeout(() => {
  dgf(456)
})


//return(只能在函数中使用), break, continue的使用
//break, continue,return都可以用于for循环，for...in，for...of,while循环，break, continue不能用于forEach,some,map,filter,every,reduce等等
function sdws(){
  for (let i = 0; i < 6; i++) {
    if (i == 3) {
      continue
      console.log("for循环", i);//不会执行
    }
    console.log("for循环", i);
  }
}
sdws()
for (let key in [1, 2, 3, 4, 5]) {
  if (key > 3) {
    continue
  }
  console.log("for in循环", key);
}
for (let value of [1, 2, 3, 4, 5]) {
  if (value > 3) {
    continue
  }
  console.log("for of循环", value);
}
let is = 10
while (is--) {
  if (is == 5) {
    continue
  }
  console.log(is)
}
//在forEach使用 return,return false,return true语句实现continue关键字的效果(跳出当前循环,后面的循环会执行)
var arr = [1, 2, 3, 4, 5];
arr.forEach(function (item) {
  if (item === 3) {
    console.log('forEach')
    return true
  }
  console.log(item);
}); //1 2 forEach 4 5

//下面这两用的不多，了解下就行
//return,return false(它会跳出当前本次循环，后面的循环会执行) return true(它会跳出整个循环，后面的循环不会再执行)
var arr = [1, 2, 3, 4, 5];
arr.some(item => {
  if (item === 4) {
    return true
  }
  console.log(item);
});//1 2 3
//return false 跳出整个循环，return ture 也需要写
var arr = [1, 2, 3, 4, 5];
arr.every(item => {
  if (item === 4) {
    return false
  }
  console.log(item);
  // 如果没有return true 的话，直接输出 1 后，跳出循环
  return true
});
//return,return false(它会跳出当前本次循环，后面的循环会执行) return true(它会跳出整个循环，后面的循环不会再执行)


//在for循环，for...in，for...of,while循环中使用return与break效果一样，都是直接跳出整个循环，但是要使用return，for循环，for...in，for...of,while循环必须在一个函数中
let ss = (function () {
  let arr = [1, 2, 3, 4, 5]
  // for (let i = 0; i < arr.length; i++) {
  //   console.log(arr[i])
  //   if (arr[i] == 3) {
  //     console.log('for')
  //     return 55
  //   }
  // }
  for (let value of [1, 2, 3, 4, 5]) {
    if (value == 3) {
      return 55
    }
    console.log("for of循环", value);
  }
  // for (let key in [1, 2, 3, 4, 5]) {
  //   if (key == 3) {
  //     return 55
  //   }
  //   console.log("for in循环", key);
  // }
  // let is = 10
  // while (is--) {
  //   if (is == 5) {
  //     return 55
  //   }
  //   console.log(is)
  // }
})();
console.log('---------------------', ss);//ss是55

// forEach方法的返回值是undefined
let ww = (function () {
  let arr = [1, 2, 3, 4, 5]
  return arr.forEach(item => {
    if (item == 3) {
      console.log('forEach')
      return 33
    }
    console.log(item)
  })
})();
console.log('---------------------', ww);//ww是undefined


//求和的两种方式1.递归 2.reduce
// let ssa=[1,2,3,4]
// function sad(...args) {
//   return args.length ? args.pop() + sad(...args) : 0
// }
// console.log(sad(...ssa));

// let sunm = [1, 2, 3, 4].reduce((a, b) => a + b)
// console.log(sunm);


//in和hasOwnProperty区别(in会检测原型上的属性，hasOwnProperty不会)
// let a = { url: "houdunren.com" }
// let b = { name: "houdunren" }
// Object.setPrototypeOf(a,b)
// console.log("name" in a);//
// console.log(a.hasOwnProperty("name"));


// //类中的私有属性
// class Uste{
//   #host = "123" //私有属性
//   #tem() {//私有方法
//     console.log(123)
//   }
//   constructor() {

//   }
// }
// let sw = new Uste()
// sw.#host = 1232
// sw.#tem()


//构造函数和类中的继承(重要需要掌握)
function Asas() {

}
Asas.prototype.show = function () {
  console.log(1111)
}
function Bsf() {

}
Bsf.prototype.show = function () {
  console.log(222)
}
// Object.setPrototypeOf(Asas.prototype, Bsf.prototype)和Asas.prototype.__proto__=Bsf.prototype是一样的
Asas.prototype = Object.create(Bsf.prototype)
const asas = new Asas()
console.log(asas.show())
console.dir(Asas)
class Qwe {
  show() {
    console.log(123, this)
  }
}
class Awe extends Qwe {
  static asd = "45210"
  dgdf = 1230
  constructor() {
    super() //super()相当于A.prototype.constructor.call(this)
  }//constructor可以不写，会默认被添加
  show() {
    //super作为对象时，在普通方法中，指向父类的原型对象；在静态方法中，指向父类
    //在子类普通方法中通过super调用父类的方法时，方法内部的this指向当前的子类实例
    super.show()//Qwe.prototype.show()  
    console.log(963)
  }
  //在子类的静态方法中通过super调用父类的方法时，方法内部的this指向当前的子类，而不是子类的实例
  static tem() {
    console.log(145)
  }
}
//类中的普通方法在Awe.prototype上，普通属性在实例对象上，静态属性和方法都在Awe类(构造函数)上
//类的继承用的是Awe.__proto__=Qwe和Object.setPrototypeOf(Awe.prototype,Qwe.prototype) 两个
const awe = new Awe()
console.log(awe.show(), awe.__proto__.__proto__);
console.log(Awe.tem(), Awe.asd, awe)
console.dir(Awe)


console.log(sum(1, 2)(3)(4)(2, 3, 5));
function sum(...args1) {
  let list = []
  list.push(...args1)
  return function (...args2) {
    list.push(...args2)
    return function (...args3) {
      list.push(...args3)
      return function (...args4) {
        list.push(...args4)
        return list.reduce((pre, next) => {
          return pre + next
        })
      }
    }
  }
}


//Promise.all并发的缺陷：
//这时候考虑一个场景：如果你的promises数组中每个对象都是http请求，或者说每个对象包含了复杂的调用处理。而这样的对象有几十万个那么会出现的情况是，
//你在瞬间发出几十万http请求（tcp连接数不足可能造成等待），或者堆积了无数调用栈导致内存溢出,这时候，我们就需要考虑对并发做限制。
//使用promise.race和Promise.allSettled，limit控制并发请求数量，limit表示当前最大请求数量
function sleeps(delay, number) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(number);
      resolve(number)
    }, delay);
  })
}
const somelists = [() => sleeps(1000, 1), () => sleeps(500, 2), () => sleeps(300, 3), () => sleeps(400, 4)]
async function dosomesing(fns, limit = 3) {
  const resultLists=[]
  const lists = new Set()
  for (const [index,fn] of Object.entries(fns)) {
    const promise = fn()
    lists.add(promise)
    promise.then(res=>{
      lists.delete(promise)
      resultLists.push({
        index,
        res,
        status: 'fulfilled'
      })
    }).catch(err=>{
      lists.delete(promise)
      resultLists.push({
        index,
        err,
        status: 'rejected'
      })
    })
    if (lists.size >= limit) {
      try {
        await Promise.race(lists)
      } catch (err) {
        console.log(err)
      }
    }
  }
  await Promise.allSettled(lists)
  resultLists.sort((a,b)=>a.index-b.index)
  return resultLists
}
dosomesing(somelists).then(res=>{
  console.log(res,5000);//res是somelists数组按顺序的接口请求结果
})

async function sgdff(){
  console.log(123);
}
console.log(sgdff());   
console.log(234);

//生成唯一标识
1.
const algorithm=()=>{
  let abc=['a','b','c','d','e','f','g','h','i','g','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];
  let [max,min]=[Math.floor(Math.random()*(10-7+1)+1),Math.floor(Math.random()*(17-10+1)+17)];
  abc=abc.sort(()=>0.4-Math.random()).slice(max,min).slice(0,8).join("");
  var a=new Date().getTime()+abc;
  return a
}
2.
const onlyVal=()=>{
  let ran=Math.random()*new Date();
  return ran.toString(16).replace(".","")
}


//finally用法的注意事项
// res 的值是 undefined
Promise.resolve(2).then(() => {}, () => {}).then(res=>{
  console.log(res);
})

// res 的值是 2
Promise.resolve(2).finally(() => {}).then(res=>{
  console.log(res);
})

// err 的值是 undefined
Promise.reject(3).then(() => {}, () => {}).then(err=>{
  console.log(err);
})

// err 的值是 3
Promise.reject(3).finally(() => {}).catch(err=>{
  console.log(err);
})


//函数中try catch finally中代码的执行顺序(http://t.csdnimg.cn/AGM5p)
// try{...}包含块中的代码有错误，则运行catch(err){...}内的代码，
// 否则不运行catch(err){...}内的代码
try{
  asas()
}catch(err){
  console.log(err,1111);
}
console.log(222222);

asas()
console.log(33333);

async function f() {
  try{
    // Promise.reject(200)
    await Promise.reject('出错了');
    //throw "1230"
    //asas.a()
  }catch(err){
    console.log(err,5300);
    return 1230
  }finally{
    console.log(111);
  }
}
f() //浏览器中执行顺序：出错了,5300   111  Promise {<fulfilled>: 1230}

function qse() {
  console.log(123856);
  return 600
}

function wer() {
  try {
    //let qwe = qse()
    return qse()
  } finally {
    console.log(456);
    //return 300 //finally中有return就只执行finally中的return，try或catch中的return都不会执行了,finally没有return那就执行try或catch中的return
  }
}
console.log(wer());//123856 456 600
let test1 = () => { try{ return 123 } finally { return 789 } }
console.log(test1()); //789
let test2 = () => { try{ throw 123 } finally { return 789 } }
console.log(test2());
function ssasd() {
  try {
    // var a = 1;
    console.log(a);  //打印a
    return 410
    console.log(d);  //打印d
  } catch (err) {
    console.log("捕获到了错误a:" + err);
    console.log(b);  //此时b未定义，没有finally的话就会报错
  } finally { //try,catch,finally中有return或代码报错，或throw错误，finally中的return或代码报错或throw错误会覆盖try,catch中的return或代码报错或throw错误
    console.log("finally执行");
    // console.log(c);  //此时c未定义,会报错；
    // return 120
  }
}
// ssasd()
console.log(ssasd(),123000)

// 使用try catch throw跳出forEach循环
try {
  [1,2,3].forEach(function(item,index){
      if(item == 2){
          throw new Error("Error"); //结束循环
      }
      console.log(item);
  })
} catch(e){
console.log(e,111);
}finally{
  console.log("finally",22222);
}
//函数中try catch finally中代码的执行顺序



// for循环数组(arr)并使用splice删除符合数组(arr)条件的某个元素(使用倒序循环这种方式,js中的forEach,for of for in都不行，因为他们底层实现都是基于正序for循环i++)
//数组中删除符合条件的某个元素推荐使用filter方法

//正向循环结果有问题
let arrs = [1,3,3, 2,4, 5];
let len=arrs.length
for (let i = 0; i < len; i++) {  
    if (arrs[i] === 3) {  
      arrs.splice(i, 1); // 删除元素  
    }  
}
console.log(arrs)


let arr = [1, 1,2, 1,3]
let tems=arr.filter(v=>v!==1) //推荐使用filter方法，简单准确
for (let i = arr.length-1; i>=0; i--) { //倒序循环也可以，就是有些复杂
      if (arr[i] === 1) {
          arr.splice(i, 1);
      };
};
console.log(arr);

//深度冻结
// methods:{
//   deepFreeze(object) {
//     // 获取对象的属性名
//     const propNames = Reflect.ownKeys(object);
  
//     // 冻结自身前先冻结属性
//     for (const name of propNames) {
//       const value = object[name];
  
//       if ((value && typeof value === "object") || typeof value === "function") {
//         this.deepFreeze(value);
//       }
//     }
  
//     return Object.freeze(object);
//   }
// }

// let x=1
// let y=2
// // let [x,y]=[y,x] //错误写法
// [x,y]=[y,x] //正确写法


//过期闭包(https://segmentfault.com/a/1190000020805789)参考这个链接内容
function createIncrement(i) {
  let value = 0;
  function increment() {
    value += i;
    console.log(value);
    const message = `Current value is ${value}`;
    return function logValue() {
      console.log(message);
    };
  }
  
  return increment;
}
const inc = createIncrement(1);
inc()(); // 打印 1
inc()();             // 打印 2
inc()();


//set和map的forEach用法
const set=new Set([1,2])
set.forEach((item,index)=>{
  console.log(item,index);//1 1  2 2  item(键值)和index(键名)是相同的
})

// 在node中报错 用run code的时候报错，在浏览器中正常
set.keys().forEach((item,index)=>{
  console.log(item,index);// 1 0  2 1  遍历方式和数组一样
})
set.values().forEach((item,index)=>{
  console.log(item,index);// 1 0  2 1  遍历方式和数组一样
})
set.entries().forEach((item,index)=>{
  console.log(item,index);// [1,1] 0  [2,2] 1  键名和键值是相等的, 遍历方式和数组一样
})
// 在node中报错 用run code的时候报错，在浏览器中正常

const map=new Map([[1,2],[3,4]])
map.forEach((item,index)=>{
  console.log(item,index);//2 1  4 3  item(键值)和index(键名)
})
map.keys().forEach((item,index)=>{
  console.log(item,index);// 1 0  3 1  遍历方式和数组一样
})
map.values().forEach((item,index)=>{
  console.log(item,index);// 2 0  4 1  遍历方式和数组一样
})
map.entries().forEach((item,index)=>{
  console.log(item,index);// [1,2] 0  [3,4] 1   遍历方式和数组一样
})
Object.entries(a) //a参数的类型是对象或带有length属性的类数组
Object.fromEntries(a) //a参数的类型是可迭代的对象Iterable

// JS---使用parseInt()和parseFloat()转换数字
// parseInt()和parseFloat()也是把其他数据类型转换为number类型的。但是它们的处理原理和Number()完全不一样。
// 它们是把字符串类型转换为数字类型，如果处理的值不是字符串，需要先转换为字符串，然后再去转换为number类型。
// 它们的转换规则是：从字符串最左边开始查找，把找到的有效数字字符转换为数字，一直遇到一个非有效数字字符为止，则结束查找
// console.log(Number('12px')) // => NaN
// console.log(parseInt('12px')) // => 12
// console.log(parseInt('12px345')) // => 12
// console.log(parseInt('12.5px')) // => 12
// console.log(parseFloat('12.5px')) // => 12.5  parseFloat只比parseInt多识别一个小数点


async function async1(){
  console.log(1);
  console.log(await async2());
  console.log(3);
}

async function async2(){
    console.log(2);
    return await 500   //这里不加await 整个结果是 1 2 Promise1 500 3 Promise2 Promise3 Promise4  加了await结果是 1 2 Promise1 Promise2 500 3 Promise3 Promise4
}

async1()
new Promise(resolve=>{
  console.log('Promise1');
  resolve()
}).then(res=>{
  console.log('Promise2');
}).then(res=>{
  console.log('Promise3');
}).then(res=>{
  console.log('Promise4');
})


//这3种情况可以好好理解下
let i=0
while(i<5){
  // function ssgf(){
  //   setTimeout(()=>{
  //     setTimeout(()=>{
  //       setTimeout(()=>{
  //         console.log(i,"00000")
  //       })
  //     })
  //   })
  // }
  // ssgf()//5 5 5 5 5 
  //注意函数传参和不传的区别
  function ssgf(i){
    setTimeout(()=>{
      setTimeout(()=>{
        setTimeout(()=>{
          console.log(i,"00000")
        })
      })
    })
  }
  ssgf(i) //0 1 2 3 4
 i++
}
for(let value of [1,2,3,4]){
  const gdff=value+'sss'
  console.log(gdff,1111)
  function ssgf(){
    setTimeout(()=>{
      setTimeout(()=>{
        setTimeout(()=>{
          console.log(value,1111)
        },1000)
      })
    })
  }
  ssgf()
}
function ssgf(){
[1,2,3,4].forEach(value=>{
    setTimeout(()=>{
      setTimeout(()=>{
        setTimeout(()=>{
          console.log(value,222222)
        })
      })
    })
  })
}
ssgf()
//这3种情况可以好好理解下


// 当你在函数内部的for󠁪循环中使用return󠁪时，当代码执行到return时,它会立即终止循环以及整个函数的执行
// function add(a, b) {
//   return a + b; // 先计算 a + b 的值，然后返回这个计算结果，所以，简而言之，是先执行表达式，然后return这个表达式的值
// }

// let result = add(2, 3); // 这里 result 将会是 5，因为函数执行了 return 表达式 2+3 并返回了结果


//获取一个变量i的余数i%5 它的余数是0，1，2，3，4  规律总结：%后面的值是4 余数就是0123 ，%后面的值是3 余数就是012 

//当直接使用 == 或 === 操作符比较两个对象时，实际上比较的是这两个对象的引用是否指向同一个内存地址，而不是比较它们内部的属性值是否相等
//Date.now()获取当前时间的毫秒级时间戳

//createRenderer渲染dom和cavas过程学习下(可以有时间再学，崔晓瑞视频)
//正则(可以有时间再学，可以看下王红元视频)
//向军大叔路由视频(有时间再学)


//js 中对象的访问器在对象冻结之后为什么还可以访问
在 JavaScript 中，访问器属性（由 get 和 set 方法定义的属性）是对象的一部分，但它们并不存储值，而是提供获取和设置值的途径。
即使一个对象被 Object.freeze() 方法冻结，访问器属性仍然可以被访问,
不存储值：访问器属性不直接存储值，它们只是定义了当属性被访问或赋值时应执行的操作。因此，即使对象被冻结，访问这些属性不会违反对象的不可变性



{
  let a=1
  if(a==1){
   a=2
   console.log(100)
  }
  else if(a==2){
    console.log(200)
  }
}

function buildTree(data, parentId = 0) {
  return data
    .filter(item => item.pid === parentId)
    .map(item => ({
      ...item,
      children: buildTree(data, item.id)
    }));
}

const flatData = [
  { id: 1, pid: 0, name: "body" },
  { id: 2, pid: 1, name: "title" },
  { id: 3, pid: 2, name: "div" }
];

const treeData = buildTree(flatData);

console.log(treeData);



function multiplication(x) {
  let result = x;

  function multiply(y) {
    if (y !== undefined) {
      result *= y;
      return multiply;
    } else {
      return result;
    }
  }
  return multiply;
}

// 测试
console.log(multiplication(4)()); // 4
console.log(multiplication(4)(5)()); // 20
console.log(multiplication(4)(5)(6)()); // 120




{
  //ES6模块
  1.ES6模块的一个基本机制：一个模块无论被多少个地方导入，导入多少次，模块只会被加载和执行一次,这确保了模块的单例特性(单例特性指的是这个模块只被实例化一次)
  2.页面刷新后，模块内的代码会重新加载和执行。这是因为浏览器在每次刷新页面时，会重新加载所有资源，包括模块。这意味着之前缓存的模块状态会被清除，下一次加载时模块会重新执行其代码

}


{
  在JavaScript中，表达式1 || 2不会读取第二个操作数2，而是直接返回第一个操作数1。这是因为逻辑或运算符||具有‌短路求值‌特性
  若第一个操作数为‌真值‌（如1），则直接返回该值，不再计算第二个操作数。
  若第一个操作数为‌假值‌（如0、null等），才会继续计算并返回第二个操作数
}


{
  在 JavaScript 中，表达式 aa = bb = 100 是一个‌连续赋值‌操作，它的执行顺序和含义如下：
  Step 1‌: bb = 100

  先执行 bb = 100，将 100 赋值给变量 bb。
  这个表达式的结果是 100（赋值操作会返回被赋的值）。
  ‌Step 2‌: aa = (bb = 100)

  由于 bb = 100 返回 100，所以 aa 也被赋值为 100。
  最终，aa 和 bb 的值都变成了 100
}


{
  在JavaScript中，函数参数的顺序必须遵循以下规则，否则会导致语法错误或逻辑问题：

  ‌必选参数（必需参数）‌
  必须放在参数列表的最前面。

  ‌可选参数（默认参数）‌
  可以放在必选参数之后，剩余参数之前。如果调用时未传值，则使用默认值。

  ‌剩余参数（...rest）‌
  必须放在参数列表的最后，且一个函数只能有一个剩余参数。


  {
  function ssd(a, ...rest,d=10) { //报错 剩余参数必须是最后一个形式参数
    console.log(a, rest, d);
  }
  ssd(1, 2, 3, 4, 5); // 输出：1 2 3
}
}







































































































